Line 2: |
Line 2: |
| local Utils = {} | | local Utils = {} |
| | | |
− | function Utils.method(frame) | + | -- * Function functions :V |
− | local m = require("Module:" .. frame.args[1]) | + | |
− | local f = frame.args[2] | + | function Utils.id(...) |
− | local args = {} | + | return ... |
− | for k, v in ipairs(frame.args) do | + | end |
− | if type(k) == "number" and k >= 3 and type(v) == "string" then
| + | |
− | table.insert(args, v)
| + | function Utils.second(_, x) |
| + | return x |
| + | end |
| + | |
| + | -- * Number functions. |
| + | |
| + | function Utils.round(x) |
| + | return math.floor(x + 0.5) |
| + | end |
| + | |
| + | function Utils.divMod(x, y) |
| + | return math.floor(x / y), x % y |
| + | end |
| + | |
| + | -- * Collection functions. |
| + | |
| + | function Utils.isize(xs) |
| + | local r = 0 |
| + | for _ in ipairs(xs) do |
| + | r = r + 1 |
| + | end |
| + | return r |
| + | end |
| + | |
| + | function Utils.size(xs) |
| + | local r = 0 |
| + | for _ in pairs(xs) do |
| + | r = r + 1 |
| + | end |
| + | return r |
| + | end |
| + | |
| + | function Utils.removekey(xs, k) |
| + | local e = xs[k] |
| + | xs[k] = nil |
| + | return e |
| + | end |
| + | |
| + | function Utils.isArray(xs) |
| + | for k, _ in pairs(xs) do |
| + | return k == 1 |
| + | end |
| + | return true |
| + | end |
| + | |
| + | function Utils.findBy(xs, p) |
| + | if Utils.isArray(xs) then |
| + | for k, v in ipairs(xs) do |
| + | if p(v, k) then |
| + | return v, k |
| + | end |
| + | end |
| + | else |
| + | for k, v in pairs(xs) do |
| + | if p(v, k) then |
| + | return v, k |
| + | end |
| + | end |
| + | end |
| + | return nil |
| + | end |
| + | |
| + | function Utils.find(tbl, v_, k_) |
| + | for _, v in pairs(tbl) do |
| + | if k_ and v and v[k_] == v_ or not k_ and v == v_ then |
| + | return v |
| end | | end |
| end | | end |
− | return m[f](m, unpack(args)) | + | return false |
| end | | end |
| | | |
− | function Utils.find(tbl, v_, k_) | + | function Utils.includes(tbl, v_, k_) |
| for _, v in pairs(tbl) do | | for _, v in pairs(tbl) do |
− | if k_ and v[k_] == v_ or not k_ and v == v_ then | + | if k_ and v and v[k_] == v_ or not k_ and v == v_ then |
| return true | | return true |
| end | | end |
Line 25: |
Line 90: |
| function Utils.map(tbl, fn) | | function Utils.map(tbl, fn) |
| local result = {} | | local result = {} |
− | for _, v in pairs(tbl) do | + | for k, v in pairs(tbl) do |
− | table.insert(result, fn(v)) | + | table.insert(result, fn(v, k)) |
| end | | end |
| return result | | return result |
Line 32: |
Line 97: |
| | | |
| function Utils.filter(tbl, pred) | | function Utils.filter(tbl, pred) |
| + | local result = {} |
| + | for k, v in pairs(tbl) do |
| + | if pred(v, k) then |
| + | table.insert(result, v) |
| + | end |
| + | end |
| + | return result |
| + | end |
| + | |
| + | function Utils.first(tbl) |
| + | for k, v in pairs(tbl) do |
| + | return k, v |
| + | end |
| + | end |
| + | |
| + | function Utils.keys(tbl) |
| + | local result = {} |
| + | for k, _ in pairs(tbl) do |
| + | table.insert(result, k) |
| + | end |
| + | return result |
| + | end |
| + | |
| + | function Utils.values(tbl) |
| local result = {} | | local result = {} |
| for _, v in pairs(tbl) do | | for _, v in pairs(tbl) do |
− | if pred(v) then | + | table.insert(result, v) |
| + | end |
| + | return result |
| + | end |
| + | |
| + | function Utils.ifind(arr, v_) |
| + | for i, v in ipairs(arr) do |
| + | if v == v_ then |
| + | return i |
| + | end |
| + | end |
| + | return false |
| + | end |
| + | |
| + | function Utils.ifindBy(arr, p) |
| + | for i, v in ipairs(arr) do |
| + | if p(v) then |
| + | return v, i |
| + | end |
| + | end |
| + | return false |
| + | end |
| + | |
| + | function Utils.imax(arr, def) |
| + | local maxValue |
| + | local maxIndex |
| + | for i, v in ipairs(arr) do |
| + | if not maxValue or v > maxValue then |
| + | maxValue = v |
| + | maxIndex = i |
| + | end |
| + | end |
| + | return maxValue or def |
| + | end |
| + | |
| + | function Utils.imin(arr, def) |
| + | local minValue |
| + | local minIndex |
| + | for i, v in ipairs(arr) do |
| + | if not minValue or v < minValue then |
| + | minValue = v |
| + | minIndex = i |
| + | end |
| + | end |
| + | return minValue or def |
| + | end |
| + | |
| + | function Utils.imap(arr, fn) |
| + | local result = {} |
| + | for i, v in ipairs(arr) do |
| + | table.insert(result, fn(v, i)) |
| + | end |
| + | return result |
| + | end |
| + | |
| + | function Utils.ifilter(arr, pred) |
| + | local result = {} |
| + | for i, v in ipairs(arr) do |
| + | if pred(v, i) then |
| table.insert(result, v) | | table.insert(result, v) |
| end | | end |
Line 41: |
Line 188: |
| end | | end |
| | | |
− | function Utils.ifirst(tbl) | + | function Utils.ifirst(arr) |
− | for k, v in ipairs(tbl) do | + | for k, v in ipairs(arr) do |
| return k, v | | return k, v |
| end | | end |
| end | | end |
| | | |
− | function Utils.first(tbl) | + | function Utils.ilast(arr) |
− | for k, v in pairs(tbl) do | + | return arr[#arr] |
− | return k, v | + | end |
| + | |
| + | function Utils.insertNew(arr, el) |
| + | if not Utils.find(arr, el) then |
| + | table.insert(arr, el) |
| + | end |
| + | end |
| + | |
| + | function Utils.concat(arr1, arr2) |
| + | for i = 1, #arr2 do |
| + | arr1[#arr1 + 1] = arr2[i] |
| + | end |
| + | return arr1 |
| + | end |
| + | |
| + | Utils.join = table.concat |
| + | |
| + | function Utils.ijoin(arr, sep) |
| + | sep = sep or "" |
| + | return table.concat(arr, sep) |
| + | end |
| + | |
| + | function Utils.joinLines(arr) |
| + | return table.concat(arr, "\n") |
| + | end |
| + | |
| + | function Utils.icopy(arr) |
| + | return Utils.imap(arr, function(v) return v end) |
| + | end |
| + | |
| + | function Utils.sort(tbl, f) |
| + | table.sort(tbl, f) |
| + | return tbl |
| + | end |
| + | |
| + | function Utils.isort(arr, f) |
| + | local result = Utils.icopy(arr) |
| + | table.sort(result, f) |
| + | return result |
| + | end |
| + | |
| + | function Utils.isum(arr, result) |
| + | result = result or 0 |
| + | for _, v in ipairs(arr) do |
| + | result = result + (v or 0) |
| end | | end |
| + | return result |
| + | end |
| + | |
| + | -- * Number functions. |
| + | |
| + | function Utils.round(x) |
| + | return x % 2 ~= 0.5 and math.floor(x + 0.5) or x - 0.5 |
| + | end |
| + | |
| + | -- * String functions. |
| + | |
| + | function Utils.pad(s, n, c) |
| + | c = c or " " |
| + | n = n or 0 |
| + | s = tostring(s) or "" |
| + | return #s < n and string.rep(c, n - #s) .. s or s |
| + | end |
| + | |
| + | function Utils.trim(s) |
| + | return string.gsub(s, "^%s*(.-)%s*$", "%1") |
| + | end |
| + | |
| + | function Utils.startsWith(s, ss) |
| + | return string.sub(s, 1, string.len(ss)) == ss |
| + | end |
| + | |
| + | -- Capitalize each word in a string. |
| + | function Utils.capitalize(s) |
| + | s = s:gsub("(%s)(%l)", function(a, b) return a .. string.upper(b) end) |
| + | s = s:gsub("^(%l)", function(a) return string.upper(a) end) |
| + | return s |
| + | end |
| + | |
| + | -- * Wikitext/HTML functions. |
| + | |
| + | function Utils.category(name) |
| + | return "[[" .. "Category:" .. name .. "]]" |
| + | end |
| + | |
| + | function Utils.red(s) |
| + | return Utils.format{[[<span style="color:red">${s}</span>]], s = s} |
| + | end |
| + | |
| + | -- * Calling arbitrary Lua functions using #invoke. |
| + | |
| + | -- Used to call Formatting:tooltip in Template:Tooltip, mainly because Lua code properly escapes characters, |
| + | -- so that span's title attribute always works. |
| + | function Utils.method(frame) |
| + | local m = require("Module:" .. frame.args[1]) |
| + | local f = frame.args[2] |
| + | local args = {} |
| + | for k, v in ipairs(frame.args) do |
| + | if type(k) == "number" and k >= 3 and type(v) == "string" then |
| + | table.insert(args, v) |
| + | end |
| + | end |
| + | return m[f](m, unpack(args)) |
| end | | end |
| + | |
| + | -- * Frame functions. |
| | | |
| local getArgs = require("Module:GetArgs") | | local getArgs = require("Module:GetArgs") |
| | | |
− | function Utils.context(frame) | + | -- Unused. |
− | local parentFrame = frame:getParent() | + | function Utils.getContext(frame) |
− | local args = getArgs{frame = parentFrame} | + | local frame1 = frame:getParent() |
− | args.pagename = parentFrame:getTitle() | + | if frame1 then |
− | return args | + | local frame2 = frame1:getParent() |
| + | if frame2 then |
| + | return { pagename = frame2:getTitle(), args = getArgs{ frame = frame2 } } |
| + | else |
| + | return { pagename = frame1:getTitle(), args = getArgs{ frame = frame1 } } |
| + | end |
| + | else |
| + | return { pagename = frame:getTitle(), args = getArgs{ frame = frame } } |
| + | end |
| + | end |
| + | |
| + | -- getParent -> getArgs |
| + | function Utils.getParentArgs(frame) |
| + | local frame1 = frame:getParent() |
| + | if frame1 then |
| + | return getArgs{ frame = frame1 } |
| + | else |
| + | return nil |
| + | end |
| + | end |
| + | |
| + | -- getArgs + getParent -> getArgs, "implicit" args can be defined in the template (e.g. pagename={{PAGENAME}}) |
| + | -- "explicit" args are user defined. |
| + | function Utils.getTemplateArgs(frame) |
| + | local frame1 = frame:getParent() |
| + | if frame1 then |
| + | return { implicit = getArgs{ frame = frame }, explicit = getArgs{ frame = frame1 } } |
| + | else |
| + | return { implicit = getArgs{ frame = frame }, explicit = {} } |
| + | end |
| + | end |
| + | |
| + | function Utils.requireModule(name) |
| + | local success, data = pcall(function () return require(string.format("Module:%s", name)) end) |
| + | -- module without return (or empty, nil, false, true return) gives success = true, data = true |
| + | if data == true then |
| + | return false, nil |
| + | else |
| + | return success, data |
| + | end |
| + | end |
| + | |
| + | function Utils.loadData(name) |
| + | local success, data = pcall(function () return mw.loadData(string.format("Module:%s", name)) end) |
| + | -- TODO: ??? |
| + | if data == true then |
| + | return false, nil |
| + | else |
| + | return success, data |
| + | end |
| + | end |
| + | |
| + | -- * Testing functions. |
| + | |
| + | function Utils.debugPrint(x, i) |
| + | i = i or 0 |
| + | if type(x) == "table" then |
| + | for k, v in pairs(x) do |
| + | mw.log( |
| + | string.rep(" ", i) .. tostring(k) .. " : " .. type(k) .. " = " .. |
| + | (type(v) == "table" and "table" or tostring(v) .. " : " .. type(v)) |
| + | ) |
| + | if type(v) == "table" then |
| + | Utils.debugPrint(v, i + 1) |
| + | end |
| + | end |
| + | else |
| + | mw.log(tostring(x) .. " : " .. type(x)) |
| + | end |
| + | end |
| + | |
| + | local function showValue(v) |
| + | return type(v) == "string" and string.format('"%s"', v) or type(v) == "function" and '"function"' or tostring(v) |
| + | end |
| + | |
| + | function Utils.js(x, i) |
| + | i = i or 0 |
| + | local r = "" |
| + | if type(x) == "table" then |
| + | r = "{\n" |
| + | for k, v in pairs(x) do |
| + | if type(v) == "table" then |
| + | r = r .. string.rep(" ", i + 1) .. tostring(k) .. ": " .. Utils.js(v, i + 1) |
| + | else |
| + | r = r .. string.rep(" ", i + 1) .. tostring(k) .. ": " .. showValue(v) .. ",\n" |
| + | end |
| + | end |
| + | r = r .. string.rep(" ", i) .. (i == 0 and "}\n" or "},\n") |
| + | else |
| + | return showValue(x) |
| + | end |
| + | return r |
| + | end |
| + | |
| + | function Utils.registerFormatTests(obj, tests, fn) |
| + | obj.run_format_tests = function() |
| + | for _, test in ipairs(tests) do |
| + | local result = obj.format(nil, test) |
| + | mw.log(fn and fn(result) or result) |
| + | end |
| + | end |
| + | end |
| + | |
| + | function Utils.registerTableTests(obj, tests, fn) |
| + | obj.run_table_tests = function() |
| + | for _, test in ipairs(tests) do |
| + | local result = obj:Table(test) |
| + | mw.log(fn and fn(result) or result) |
| + | end |
| + | end |
| + | end |
| + | |
| + | function Utils.test(obj, desc, fn) |
| + | obj.test = function() |
| + | mw.log(desc) |
| + | fn(obj) |
| + | mw.log("ok") |
| + | end |
| + | end |
| + | |
| + | Utils.log = mw.log |
| + | |
| + | function Utils.format(s, lookup) |
| + | if not lookup then |
| + | lookup = s |
| + | s = lookup[1] |
| + | table.remove(lookup, 1) |
| + | end |
| + | return (string.gsub(s, '${([^%.%!%:%}%[%]]+)%[?([^%.%!%:%}%]]*)%]?%.?([^%!%:%}]*)!?([^%:%}]?):?([^%}]*)}', |
| + | function(name, element, attribute, conversion, format_spec) |
| + | --local start_of_value, end_of_value, value = string.find(x, '^${(.-)[[.!:}]') |
| + | --local start_of_access, end_of_access, access = string.find(x, '^${.-%[(.-)%][!:}]') |
| + | --if not access then |
| + | -- start_of_access, end_of_access, access = string.find(x, '^${[^:]-%.(.-)[!:}]') |
| + | --end |
| + | --local start_of_conversion, end_of_conversion, conversion = string.find(x, '^${.-!(.)[:}]') |
| + | --local start_of_format_spec, end_of_format_spec, format_spec = string.find(x, ':(.*)}$') |
| + | |
| + | local value = lookup[name] |
| + | if string.len(element) > 0 then |
| + | value = value[element] |
| + | elseif string.len(attribute) > 0 then |
| + | value = value[attribute] |
| + | end |
| + | |
| + | if string.len(conversion) > 0 then |
| + | if conversion == 's' then |
| + | value = tostring(value) |
| + | end |
| + | end |
| + | |
| + | if string.len(format_spec) > 0 then |
| + | local start_of_sign, end_of_sign, sign = string.find(format_spec, '([+%- ])') |
| + | local start_of_width, end_of_width, width, comma, precision, option = string.find(format_spec, '(%d*)(,?)([.0-9]*)([bcdeEfFgGnosxX%%]?)$') |
| + | precision = string.sub(precision, 2) |
| + | local number = tonumber(value) |
| + | if #width > 0 then |
| + | if number then |
| + | value = string.format(string.format(number % 1 ~= 0 and "%%0%s.%sf" or "%%0%sd", width, #precision > 0 and precision or 0), number) |
| + | end |
| + | value = string.format(string.format("%%0%ss", width), value) |
| + | elseif #precision > 0 and number then |
| + | value = string.format(string.format("%%0%s.%sf", width, precision), number) |
| + | end |
| + | if sign then |
| + | if number and number > 0 then |
| + | if sign == "+" then |
| + | value = "+" .. value |
| + | elseif sign == " " then |
| + | value = " " .. value |
| + | end |
| + | end |
| + | end |
| + | end |
| + | |
| + | return value |
| + | end)) |
| + | end |
| + | |
| + | function Utils.split(string, separator, max_split, plain) |
| + | assert(separator ~= '') |
| + | assert(max_split == nil or max_split >= 1) |
| + | |
| + | local default_separator = false |
| + | |
| + | local result = {} |
| + | |
| + | if not separator or separator == '' then |
| + | separator = '%s+' |
| + | plain = false |
| + | string = mw.text.trim(string) |
| + | if string == '' then |
| + | return result |
| + | end |
| + | end |
| + | |
| + | max_split = max_split or -1 |
| + | |
| + | local item_index, start_index = 1, 1 |
| + | local separator_start, separator_end = mw.ustring.find(string, separator, start_index, plain) |
| + | while separator_start and max_split ~= 0 do |
| + | result[item_index] = mw.ustring.sub(string, start_index, separator_start - 1) |
| + | item_index = item_index + 1 |
| + | start_index = separator_end + 1 |
| + | separator_start, separator_end = mw.ustring.find(string, separator, start_index, plain) |
| + | max_split = max_split - 1 |
| + | end |
| + | result[item_index] = mw.ustring.sub(string, start_index) |
| + | |
| + | return result |
| end | | end |
| | | |
− | function Utils.test_context(frame) | + | Utils.romanizable = {'zh-hans', 'zh-hant', 'zh-cn', 'zh-tw', 'zh-hk', 'zh-sg', 'zh-mo', 'zh-my', 'ja', 'ko', 'vi'} |
− | local context = Utils.context(frame) | + | |
− | return "arg = " + (context.arg or "?") + " @ " + (context.pagename or "?") | + | Utils.normalization_table = {[' '] = ' ', ['~'] = '~', ['!'] = '!', ['?'] = '?'} |
| + | |
| + | -- checks if string is set and if it's non-empty |
| + | function Utils.isset(target) |
| + | return target ~= nil and target ~= "" |
| + | end |
| + | |
| + | --[[ simulates the (a ? b : c) notation of C/C++ and PHP languages. |
| + | Similar to {{#if:{{{a|}}}|{{{b|}}}|{{{c|}}}}} from parser functions. ]] |
| + | function Utils.cv(a, b, c) |
| + | if a then return b else return c end |
| + | end |
| + | |
| + | --slices a table to return another table containing values within a certain range |
| + | --source: http://snippets.luacode.org/snippets/Table_Slice_116 |
| + | function Utils.sliceTable (values,i1,i2) |
| + | local res = {} |
| + | local n = #values |
| + | -- default values for range |
| + | i1 = i1 or 1 |
| + | i2 = i2 or n |
| + | if i2 < 0 then |
| + | i2 = n + i2 + 1 |
| + | elseif i2 > n then |
| + | i2 = n |
| + | end |
| + | if i1 < 1 or i1 > n then |
| + | return {} |
| + | end |
| + | local k = 1 |
| + | for i = i1,i2 do |
| + | res[k] = values[i] |
| + | k = k + 1 |
| + | end |
| + | return res |
| + | end |
| + | |
| + | -- checks if a given page exists |
| + | function Utils.exists(page) |
| + | if not Utils.isset(page) then return false end |
| + | return mw.getCurrentFrame():preprocess('{{#ifexist:' .. page .. '|1|0}}') == '1' |
| + | end |
| + | |
| + | --[[ Tries to get contents of a page with given name. |
| + | If the function fails it returns nil (page doesn't exist or can't be loaded) |
| + | On success it returns the contents of page (it can be be partially preprocessed, so watch out for parser markers). ]] |
| + | function Utils.getPage(name) |
| + | if not Utils.isset(name) then return nil end |
| + | |
| + | local frame = mw.getCurrentFrame() |
| + | |
| + | -- do a safe call to catch possible errors, like "template doesn't exist" |
| + | local stat,page = pcall(frame.expandTemplate, frame, {title = ':' .. name}) |
| + | |
| + | if not stat then |
| + | -- TODO: 'page' contains the error message. Do some debugging? |
| + | return nil |
| + | end |
| + | |
| + | return page |
| + | end |
| + | |
| + | function Utils.stripTags(text) |
| + | if Utils.isset(text) then |
| + | local tmp |
| + | repeat |
| + | tmp = text |
| + | -- a pair of tags, like <td style="">...</td> |
| + | text = string.gsub(text, '<%s*(%w+).->(.-)<%s*/%s*%1%s*>', '%2') |
| + | -- closed tag, like <br/> |
| + | text = string.gsub(text, '<%s*%w+%s*/%s*>', '') |
| + | until tmp == text |
| + | end |
| + | return text |
| + | end |
| + | |
| + | --[[ Sort table and remove repeating elements. |
| + | Since tbl is passed as reference, any changes in it will affect the passed table. ]] |
| + | function Utils.trunkTable(tbl) |
| + | table.sort(tbl) |
| + | local last |
| + | local redo |
| + | repeat |
| + | redo = false |
| + | last = nil |
| + | for k,v in pairs(tbl) do |
| + | if v ~= last then |
| + | last = v |
| + | else |
| + | table.remove(tbl, k) |
| + | redo = true |
| + | break |
| + | end |
| + | end |
| + | until not redo |
| + | end |
| + | |
| + | --[[ Checks if a given value is among the elements of a table and returns its index. |
| + | Returns nil if it can't find it. ]] |
| + | function Utils.isInTable(tbl, val) |
| + | for k,v in pairs(tbl) do |
| + | if v == val then return k end |
| + | end |
| + | return nil |
| + | end |
| + | |
| + | --[[ Compare 'n' elements in two tables, starting from 's1' in first table and 's2' in second table. ]] |
| + | function Utils.partialTableCompare(t1, t2, s1, s2, n) |
| + | if n < 1 then return true end -- basically there's nothing to compare, so no differences were found |
| + | |
| + | for i = 0,(n-1) do |
| + | -- Note that nil values are also valid. |
| + | if t1[s1+i] ~= t2[s2+i] then return false end |
| + | end |
| + | |
| + | return true |
| + | end |
| + | |
| + | -- naming based on "Navbox" elements |
| + | Utils.main_colors = { |
| + | game = {border = '#A88580', title = '#FFC9C2', above = '#FFD1CA', group = '#FFD9D2', subgroup = '#FFE1DA', dark = '#FFEEE8', background = '#FFF4EE'}; |
| + | music = {border = '#A8A077', title = '#FFF3B4', above = '#FFF6C0', group = '#FFF7C8', subgroup = '#FFF8D0', dark = '#FFFBE4', background = '#FFFBEE'}; |
| + | printwork = {border = '#9298A8', title = '#DDE6FF', above = '#E1E7FF', group = '#E6E9FF', subgroup = '#EAECFF', dark = '#EDF2FF', background = '#F4F9FF'}; |
| + | } |
| + | |
| + | -- function for easy adding of styles to html tags |
| + | function Utils.addStyle(tbl, val) |
| + | if Utils.isset(val) then |
| + | local av = tostring(val):gsub("^%s*(.-)%s*$", "%1") |
| + | if string.sub(av, -1) ~= ';' then av = av .. ';' end |
| + | tbl[#tbl+1] = av |
| + | end |
| end | | end |
| | | |
| return Utils | | return Utils |