-- This module contains a number of functions that make use of random numbers.
local cfg =
---------------------------------------------------------------------------------------- Configuration--------------------------------------------------------------------------------------
-- Set this to true if your wiki has a traffic rate of less than one edit every two minutes or so.-- This will prevent the same "random" number being generated many times in a row until a new edit is made-- to the wiki. This setting is only relevant if the |same= parameter is set.cfg.lowTraffic = false
-- If cfg.lowTraffic is set to true, and the |same= parameter is set, this value is used for the refresh rate of the random seed.-- This is the number of seconds until the seed is changed. Getting this right is tricky. If you set it too high, the same number-- will be returned many times in a row. If you set it too low, you may get different random numbers appearing on the same page,-- particularly for pages that take many seconds to process.cfg.seedRefreshRate = 60
---------------------------------------------------------------------------------------- End configuration--------------------------------------------------------------------------------------
local p = -- For functions available from other Lua modules.local l = -- For functions not available from other Lua modules, but that need to be accessed using table keys.
local yesno = require('Module:Yesno')local makeList = require('Module:List').makeList
---------------------------------------------------------------------------------------- Helper functions--------------------------------------------------------------------------------------
local function raiseError(msg) -- This helps to generate a wikitext error. It is the calling function's responsibility as to how to include it in the output. return mw.ustring.format(' error: %s.', msg)end
---------------------------------------------------------------------------------------- random number function--------------------------------------------------------------------------------------
local function getBigRandom(l, u) -- Gets a random integer between l and u, and is not limited to RAND_MAX. local r = 0 local n = 2^math.random(30) -- Any power of 2. local limit = math.ceil(53 / (math.log(n) / math.log(2))) for i = 1, limit do r = r + math.random(0, n - 1) / (n^i) end return math.floor(r * (u - l + 1)) + lend
function l.number(args) -- Gets a random number. first = tonumber(args[1]) second = tonumber(args[2]) -- This needs to use if statements as math.random won't accept explicit nil values as arguments. if first then if second then if first > second then -- Second number cannot be less than the first, or it causes an error. first, second = second, first end return getBigRandom(first, second) else return getBigRandom(1, first) end else return math.random endend
---------------------------------------------------------------------------------------- Date function--------------------------------------------------------------------------------------
function l.date(args) -- This function gets random dates, and takes timestamps as positional arguments. -- With no arguments specified, it outputs a random date in the current year. -- With two arguments specified, it outputs a random date between the timestamps. -- With one argument specified, the date is a random date between the unix epoch (1 Jan 1970) and the timestamp. -- The output can be formatted using the "format" argument, which works in the same way as the #time parser function. -- The default format is the standard Wikipedia timestamp. local lang = mw.language.getContentLanguage
local function getDate(format, ts) local success, date = pcall(lang.formatDate, lang, format, ts) if success then return date end end
local function getUnixTimestamp(ts) local unixts = getDate('U', ts) if unixts then return tonumber(unixts) end end
local t1 = args[1] local t2 = args[2] -- Find the start timestamp and the end timestamp. local startTimestamp, endTimestamp if not t1 then -- Find the first and last second in the current year. local currentYear = tonumber(getDate('Y')) local currentYearStartUnix = tonumber(getUnixTimestamp('1 Jan ' .. tostring(currentYear))) local currentYearEndUnix = tonumber(getUnixTimestamp('1 Jan ' .. tostring(currentYear + 1))) - 1 startTimestamp = '@' .. tostring(currentYearStartUnix) -- @ is used to denote Unix timestamps with lang:formatDate. endTimestamp = '@' .. tostring(currentYearEndUnix) elseif t1 and not t2 then startTimestamp = '@0' -- the Unix epoch, 1 January 1970 endTimestamp = t1 elseif t1 and t2 then startTimestamp = t1 endTimestamp = t2 end
-- Get Unix timestamps and return errors for bad input (or for bugs in the underlying PHP library, of which there are unfortunately a few) local startTimestampUnix = getUnixTimestamp(startTimestamp) local endTimestampUnix = getUnixTimestamp(endTimestamp) if not startTimestampUnix then return raiseError('"' .. tostring(startTimestamp) .. '" was not recognised as a valid timestamp') elseif not endTimestampUnix then return raiseError('"' .. tostring(endTimestamp) .. '" was not recognised as a valid timestamp') elseif startTimestampUnix > endTimestampUnix then return raiseError('the start date must not be later than the end date (start date: "' .. startTimestamp .. '", end date: "' .. endTimestamp .. '")') end
-- Get a random number between the two Unix timestamps and return it using the specified format. local randomTimestamp = getBigRandom(startTimestampUnix, endTimestampUnix) local dateFormat = args.format or 'H:i, d F Y (T)' local result = getDate(dateFormat, '@' .. tostring(randomTimestamp)) if result then return result else return raiseError('"' .. dateFormat .. '" is not a valid date format') endend
---------------------------------------------------------------------------------------- List functions--------------------------------------------------------------------------------------
local function randomizeArray(t, limit) -- Randomizes an array. It works by iterating through the list backwards, each time swapping the entry -- "i" with a random entry. Courtesy of Xinhuan at http://forums.wowace.com/showthread.php?p=279756 -- If the limit parameter is set, the array is shortened to that many elements after being randomized. -- The lowest possible value is 0, and the highest possible is the length of the array. local len = #t for i = len, 2, -1 do local r = math.random(i) t[i], t[r] = t[r], t[i] end if limit and limit < len then local ret = for i, v in ipairs(t) do if i > limit then break end ret[i] = v end return ret else return t endend
local function removeBlanks(t) -- Removes blank entries from an array so that it can be used with ipairs. local ret = for k, v in pairs(t) do if type(k)
local function makeSeparator(sep) if sep
'newline' then -- Ditto for newlines return '\n' elseif type(sep)
mwsep then return mw.message.new(sep .. '-separator'):plain end end return sep endend
local function makeRandomList(args) local list = removeBlanks(args) list = randomizeArray(list, tonumber(args.limit)) return listend
function l.item(args) -- Returns a random item from a numbered list. local list = removeBlanks(args) local len = #list if len >= 1 then return list[math.random(len)] endend
function l.list(args) -- Randomizes a list and concatenates the result with a separator. local list = makeRandomList(args) local sep = makeSeparator(args.sep or args.separator) return table.concat(list, sep)end
function l.text_list(args) -- Randomizes a list and concatenates the result, text-style. Accepts separator and conjunction arguments. local list = makeRandomList(args) local sep = makeSeparator(args.sep or args.separator) local conj = makeSeparator(args.conj or args.conjunction) return mw.text.listToText(list, sep, conj)end
function l.array(args) -- Returns a Lua array, randomized. For use from other Lua modules. return randomizeArray(args.t, args.limit)end
---------------------------------------------------------------------------------------- HTML list function--------------------------------------------------------------------------------------
function l.html_list(args, listType) -- Randomizes a list and turns it into an HTML list. Uses . listType = listType or 'bulleted' local listArgs = makeRandomList(args) -- Arguments for . for k, v in pairs(args) do if type(k)
---------------------------------------------------------------------------------------- The main function. Called from other Lua modules.--------------------------------------------------------------------------------------
function p.main(funcName, args, listType) -- Sets the seed for the random number generator and passes control over to the other functions. local same = yesno(args.same) if not same then -- Generates a different number every time the module is called, even from the same page. -- This is because of the variability of os.clock (the time in seconds that the Lua script has been running for). math.randomseed(mw.site.stats.edits + mw.site.stats.pages + os.time + math.floor(os.clock * 1000000000)) else if not cfg.lowTraffic then -- Make the seed as random as possible without using anything time-based. This means that the same random number -- will be generated for the same input from the same page - necessary behaviour for some wikicode templates that -- assume bad pseudo-random-number generation. local stats = mw.site.stats local views = stats.views or 0 -- This is not always available, so we need a backup. local seed = views + stats.pages + stats.articles + stats.files + stats.edits + stats.users + stats.activeUsers + stats.admins -- Make this as random as possible without using os.time or os.clock math.randomseed(seed) else -- Make the random seed change every n seconds, where n is set by cfg.seedRefreshRate. -- This is useful for low-traffic wikis where new edits may not happen very often. math.randomseed(math.floor(os.time / cfg.seedRefreshRate)) end end if type(args) ~= 'table' then error('the second argument to p.main must be a table') end return l[funcName](args, listType)end ---------------------------------------------------------------------------------------- Process arguments from #invoke--------------------------------------------------------------------------------------
local function makeWrapper(funcName, listType) -- This function provides a wrapper for argument-processing from #invoke. -- listType is only used with p.html_list, and is nil the rest of the time. return function (frame) -- If called via #invoke, use the args passed into the invoking template, or the args passed to #invoke if any exist. -- Otherwise assume args are being passed directly in from the debug console or from another Lua module. local origArgs if frame
'string' then v = mw.text.trim(v) end if v ~= then args[k] = v end end return p.main(funcName, args, listType) endend
-- Process arguments for HTML list functions.local htmlListFuncs = for funcName, listType in pairs(htmlListFuncs) do p[funcName] = makeWrapper('html_list', listType)end
-- Process arguments for other functions.local otherFuncs = for _, funcName in ipairs(otherFuncs) do p[funcName] = makeWrapper(funcName)end
return p