편집 요약 없음 |
Text-Justify (토론 | 기여) |
||
(사용자 3명의 중간 판 5개는 보이지 않습니다) | |||
1번째 줄: | 1번째 줄: | ||
-- | -- This module contains a number of functions that make use of random numbers. | ||
local p = {} | |||
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('<b class="error">[[Module:Random]] error: %s.</b>', msg) | |||
end | 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)) + l | |||
end | |||
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() | |||
end | |||
end | |||
-------------------------------------------------------------------------------------- | |||
-- 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') | |||
end | |||
end | |||
-------------------------------------------------------------------------------------- | |||
-- 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 | |||
end | |||
end | |||
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) == 'number' then | |||
table.insert(ret, k) | |||
end | |||
end | |||
table.sort(ret) | |||
for i, v in ipairs(ret) do | |||
ret[i] = t[v] | |||
end | |||
return ret | |||
end | |||
local function makeSeparator(sep) | |||
if sep == 'space' then | |||
-- Include an easy way to use spaces as separators. | |||
return ' ' | |||
elseif sep == 'newline' then | |||
-- Ditto for newlines | |||
return '\n' | |||
elseif type(sep) == 'string' then | |||
-- If the separator is a recognised MediaWiki separator, use that. Otherwise use the value of sep if it is a string. | |||
local mwseparators = {'dot', 'pipe', 'comma', 'tpt-languages'} | |||
for _, mwsep in ipairs(mwseparators) do | |||
if sep == mwsep then | |||
return mw.message.new( sep .. '-separator' ):plain() | |||
end | |||
end | |||
return sep | |||
end | |||
end | |||
local function makeRandomList(args) | |||
local list = removeBlanks(args) | |||
list = randomizeArray(list, tonumber(args.limit)) | |||
return list | |||
end | |||
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)] | |||
end | |||
end | |||
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 [[Module:List]]. | |||
listType = listType or 'bulleted' | |||
local listArgs = makeRandomList(args) -- Arguments for [[Module:List]]. | |||
for k, v in pairs(args) do | |||
if type(k) == 'string' then | |||
listArgs[k] = v | |||
end | |||
end | |||
return makeList(listType, listArgs) | |||
end | |||
-------------------------------------------------------------------------------------- | |||
-- 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 == mw.getCurrentFrame() then | |||
origArgs = frame:getParent().args | |||
for k, v in pairs(frame.args) do | |||
origArgs = frame.args | |||
break | |||
end | |||
else | |||
origArgs = frame | |||
end | |||
-- Trim whitespace and remove blank arguments. | |||
local args = {} | |||
for k, v in pairs(origArgs) do | |||
if type(v) == 'string' then | |||
v = mw.text.trim(v) | |||
end | |||
if v ~= '' then | |||
args[k] = v | |||
end | |||
end | |||
return p.main(funcName, args, listType) | |||
end | |||
end | |||
-- Process arguments for HTML list functions. | |||
local htmlListFuncs = { | |||
bulleted_list = 'bulleted', | |||
unbulleted_list = 'unbulleted', | |||
horizontal_list = 'horizontal', | |||
ordered_list = 'ordered', | |||
horizontal_ordered_list = 'horizontal_ordered' | |||
} | |||
for funcName, listType in pairs(htmlListFuncs) do | |||
p[funcName] = makeWrapper('html_list', listType) | |||
end | |||
-- Process arguments for other functions. | |||
local otherFuncs = {'number', 'date', 'item', 'list', 'text_list'} | |||
for _, funcName in ipairs(otherFuncs) do | |||
p[funcName] = makeWrapper(funcName) | |||
end | |||
return p | return p |
2021년 2월 19일 (금) 23:48 기준 최신판
이 문서는 모듈:Test/설명문서에서 편집가능합니다
모듈 연습장입니다. 각 모듈에서 편집하면 기존에 사용중인 모듈에 문제가 갈수도 있고 로그가 지저분해지니 여기서 연습을 하고 실제 모듈에 적용하면 될 것 같습니다.
서버
-- 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('<b class="error">[[Module:Random]] error: %s.</b>', 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)) + l
end
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()
end
end
--------------------------------------------------------------------------------------
-- 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')
end
end
--------------------------------------------------------------------------------------
-- 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
end
end
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) == 'number' then
table.insert(ret, k)
end
end
table.sort(ret)
for i, v in ipairs(ret) do
ret[i] = t[v]
end
return ret
end
local function makeSeparator(sep)
if sep == 'space' then
-- Include an easy way to use spaces as separators.
return ' '
elseif sep == 'newline' then
-- Ditto for newlines
return '\n'
elseif type(sep) == 'string' then
-- If the separator is a recognised MediaWiki separator, use that. Otherwise use the value of sep if it is a string.
local mwseparators = {'dot', 'pipe', 'comma', 'tpt-languages'}
for _, mwsep in ipairs(mwseparators) do
if sep == mwsep then
return mw.message.new( sep .. '-separator' ):plain()
end
end
return sep
end
end
local function makeRandomList(args)
local list = removeBlanks(args)
list = randomizeArray(list, tonumber(args.limit))
return list
end
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)]
end
end
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 [[Module:List]].
listType = listType or 'bulleted'
local listArgs = makeRandomList(args) -- Arguments for [[Module:List]].
for k, v in pairs(args) do
if type(k) == 'string' then
listArgs[k] = v
end
end
return makeList(listType, listArgs)
end
--------------------------------------------------------------------------------------
-- 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 == mw.getCurrentFrame() then
origArgs = frame:getParent().args
for k, v in pairs(frame.args) do
origArgs = frame.args
break
end
else
origArgs = frame
end
-- Trim whitespace and remove blank arguments.
local args = {}
for k, v in pairs(origArgs) do
if type(v) == 'string' then
v = mw.text.trim(v)
end
if v ~= '' then
args[k] = v
end
end
return p.main(funcName, args, listType)
end
end
-- Process arguments for HTML list functions.
local htmlListFuncs = {
bulleted_list = 'bulleted',
unbulleted_list = 'unbulleted',
horizontal_list = 'horizontal',
ordered_list = 'ordered',
horizontal_ordered_list = 'horizontal_ordered'
}
for funcName, listType in pairs(htmlListFuncs) do
p[funcName] = makeWrapper('html_list', listType)
end
-- Process arguments for other functions.
local otherFuncs = {'number', 'date', 'item', 'list', 'text_list'}
for _, funcName in ipairs(otherFuncs) do
p[funcName] = makeWrapper(funcName)
end
return p