Module:DynamicWelcome

--| Module:  DynamicWelcome (v1.4.0) --- Allows for customizable welcome messages. --by "The JoTS"

-- update v1.4.0 ~ Code snippet to convert wikilinks. -- update v1.3.x ~ New user function for selecting greeter.

--TODO create message library for errors --

-- [==[ Import modules ]==] -- local getArgs = require("Dev:Arguments").getArgs local yesno  = require "Dev:Yesno" local custom, messages = pcall(mw.loadData, "Module:DynamicWelcome/msg")

-- user error: msg dict exists, not formatted properly if (not custom) and messages:match("^%%") then error(messages:sub(2)) end

--TODO implement default messages --messages = custom and messages or mw.loadData "Dev:DynamicWelcome/default"

-- [==[ Meta functions ]==] --

--% Creates a wrapped function that returns an argument table to the provided --- function 'func' and processes returned text from 'func'. --@ func (function) A function to be wrapped. --: (function) ...A wrapped function. local function arg_wrap(func) return function(frame) return frame:preprocess( func(getArgs(frame)) ) end end

--% Returns the namespace of a given page name --- or removes the namespace from a given page name, if 'gsub' is true. --@ pageName (string) A page's name. --@ [gsub] (bool) If true, removes the namespace from a page name. --: (string) A string of a formatted page name, or of a namespace. local function namespaceOp(pageName, gsub) -- consider splitting into two different funcs? return string[gsub and "gsub" or "match"](           pageName,            "^([^:]+):",            gsub and '' or nil) end

--% Dynamic processing of the "pipe trick," instead of at page save time. --- See http://wikipedia.org/wiki/Help:Pipe_trick  for more details. --@ txt (string) Text to be processed. --: (string) Processed string. local function processPipeTrick(txt) -- the pipe trick involving the comma is NOT implemented return txt:gsub("%[%[([^|]+)|%]%]", function(page)       -- get page name        local name = page            :gsub ("^[%w%s_]+:", '')            :match "[%w%s_]+"            :match "^%s*(.-)%s*$"        -- return formatted link        return "' .. name .. ""    end) end

--% Validates user input. --@ provided (multiple) User input. --@ options (table)   Selection of possible inputs. --: (multiple) ...I mean, in this script, it'll be a bool/nil. --- Or, at the very least, it should be treated as such. local function validate(provided, options) return options[provided] end

-- [==[ Module Functions ]==] -- local MODES = { ["message-wall"] = true, ["message"] = true }

local VAR_ENUM = { -- Implement custom enum for vars page       = 1, greeter_talk = 2, greeter_sig = 3, welcomed    = 4 }

local SETUP_CONST = { ADMIN = true, MODE = true, ANON = true, CHANCE = true }

--% Generates a greeting message according the the provided args. --@ vargs (table) A table of raw "virtual" arguments retrieved from frame. --: (string) Generated greeting. local function _greet(vargs) if vargs[5] then -- Handle a two part "page" argument (e.g. "Thread:1234|Lorem Ipsum") -- Does NOT properly handle an error in which a user -- somehow provides 5 args vargs[1] = vargs[1] .. '|' .. vargs[2] -- merge argument back together -- shift arguments in table for i=2,4 do           vargs[i] = vargs[i + 1] vargs[i + 1] = nil end end

-- Set enumerator local args = setmetatable({}, { __index = function(t, key)       return vargs[VAR_ENUM[key] or key]    end }) assert((not args.mode) or validate(args.mode, MODES),       "An invalid mode was provided") -- defaults args.mode = (type(args.mode) ~= "string" and "message-wall" or args.mode) args.anon = yesno(args.anon) and "anon" or "user" local dict = messages[args.mode][args.anon] -- selected root dictionary -- Selected greeter dictionary ("Wikia" is fallback) local gDict = (not dict) and error("The \"" .. args.anon .. "\" dictionary does not exist") or dict[namespaceOp(args.greeter_talk, true)] or dict.Wikia or error("The default greeter dictionary,\"Wikia\", does not exist") local entry = gDict[namespaceOp(args.page)] or gDict.DEFAULT or error("The default entry does not exist") -- Return formatted entry return processPipeTrick( entry:gsub("%$(%d)", function(var) -- Substitute variable numbers w/ value return args[tonumber(var)] or var end) ) end

local greet = arg_wrap( _greet )

--% An assistive function for creating a table of greeting templates --@ admin (string) An admin to generate a greeting for. --@ mode (string) A messaging mode. "message-wall" or "message" (see MODES) --@ globals (table) Must be _G or getfenv --: (table) Formatted table local function setup(admin, mode, globals) -- errors assert(globals.ADMIN == admin,       "%Admin mismatch: " .. globals.ADMIN .. " x " .. admin) assert(globals.MODE == mode,       "%Mode mismatch: " .. globals.MODE .. " x " .. mode) assert(validate(mode, MODES),       "%An invalid mode was provided: " .. mode) assert(type(globals.ANON) == "boolean",       "%Global \"ANON\" was not initialized or is not a boolean") assert(type(globals.CHANCE) == "number" or globals.CHANCE == nil,       "%Global \"CHANCE\" is defined, but is not a number value") local frm = globals.__DYNAWELC__ or {} -- formatted table local anon = globals.ANON and "anon" or "user" -- setup globals.__DYNAWELC__ = frm frm[mode] = frm[mode] or {} frm[mode][anon] = frm[mode][anon] or {} frm[mode][anon][admin] = frm[mode][anon][admin] or {} -- setup greeter probability if admin ~= "Wikia" then frm[0] = frm[0] or {} table.insert(frm[0], {admin, globals.CHANCE or 1}) globals.CHANCE = nil end -- create entry for k,v in pairs(globals) do       if k:match("^(%u[%w_]+)$") and (not validate(k, SETUP_CONST)) then globals[k] = nil frm[mode][anon][admin][k:gsub('_',' ')] = v:gsub("\\n", "\n") end end

return frm end

--% Selects a greeter based of chance and probability... or something --: (string) Greeter's username local function user local probs = messages[0] or {} -- probabilities local totalProb = 0; for _,t in ipairs(probs) do       totalProb = totalProb + t[2] end totalProb = math.ceil(totalProb) math.randomseed(os.time) local rand = math.random local probSum = 0.0 for _,t in ipairs(probs) do       probSum = probSum + (t[2] / totalProb) if probSum >= rand then return t[1] end end

-- something went wrong? return "@latest" end

--% Converts a wikilink to a regular link (invoked in page source) --- Prevents potential entry into Special:WantedPages upon deletion --- Usage: --@ frame (frame) Frame object. --: (string) Formatted link function link(frame) local args = frame.args if (not args[2]) then args = mw.text.split(args[1], '|') end return frame:preprocess(       "[ " .. (args[2] or args[1]) .. "]") end

--- Module interface --- return { -- en   greet = greet,    setup = setup, user = user,     link  = link }