Module:Exclusive

---Holds the tables with the l10n information for the different languages, taken from the l10n submodule. local l10n_data = mw.loadData('Module:Exclusive/l10n')

---Database with exclusivity info. local exclusive_info = mw.loadData('Module:Exclusive/data')

local bit32 = require('bit32') local trim = mw.text.trim

---Default content language of the wiki (i.e. `$wgLanguageCode`, not the value of the ---`uselang` URL parameter, and not the user's language preference setting). local contentLanguage = mw.getContentLanguage

---Holds the arguments from the template call. local args_table

---The current language. Determines which l10n table to use. local lang

---Return the l10n string associated with the `key`. ---@param key string ---@return string local function l10n(key) if l10n_data[lang] then return l10n_data[lang][key] or l10n_data['en'][key] else return l10n_data['en'][key] end end

---Return a trimmed version of the value of the template parameter with the specified `key`. ---Return `nil` if the parameter is empty or unset. ---@param key string|number ---@return string|nil local function getArg(key) local value = trim(args_table[key] or '') return (value ~= '') and value or nil end

---Convert a string of parameters in a `@param1:value^@param2:value^` format to a table. ---@param paramstr string ---@return table local function parse(paramstr) local args = {} for s in string.gmatch(paramstr, '%b@^') do		local k,v = string.match(s, '^@(.-):(.*)^$') args[k] = v	end return args end

---Split the `str` on each `div` in it and return the result as a table. ---This is much much faster then `mw.text.split`. ---Credit: http://richard.warburton.it. ---@param div string ---@param str string ---@return table|boolean local function explode(div,str) if (div=='') then return false end local pos,arr = 0,{} -- for each divider found for st,sp in function return string.find(str,div,pos,true) end do		arr[#arr + 1] = string.sub(str,pos,st-1) -- Attach chars left of current divider pos = sp + 1 -- Jump past current divider end arr[#arr + 1] = string.sub(str,pos) -- Attach chars right of last divider return arr end

---Return the integer defined in the database for the specified `page`. ---Perform some standardization on the `page` for that first. ---@param page string ---@return number local function readFromDb(page) -- standardize pagename: remove section parts ('x#section' -> 'x') and replace underscores with spaces page = contentLanguage:ucfirst(string.gsub(string.gsub(page or , '#.*', ), '_', ' ')) return exclusive_info[page] or 0 end

---Override the exclusivity information in `info` ---with the content of `dcom3j`. ---@param info number ---@param dcom3j string Expected format: `::::<3>:`, with each platform either a Boolean string ("y", "0", etc.) or an empty string ---@return number local function override(info, dcom3j) for k, v in pairs(explode(':', dcom3j)) do		if v ~= '' then if v == '1' or v == 'y' or v == 'yes' then info = bit32.replace(info, 1, k-1) elseif v == '0' or v == 'n' or v == 'no' then info = bit32.replace(info, 0, k-1) end end end return info

-- Example to demonstrate the behavior of this function: -- Goal: Change "dcom" to "dco3". -- info = 15 ("dcom"), dcom3j = ":::no:yes:" -- decimal 15 = binary 001111 -- The first "no" is at index 3 in the dcom3j string, so -- replace the corresponding bit with a 0: 001111 -> 000111. -- The "yes" is at index 4, so replace the bit at index 4 -- with a 1: 000111 -> 010111. -- The result is info = 23 ("dco3"). end

---Main function to retrieve exclusivity information. ---@param page string The entity to get the info about. ---@param invert boolean Whether to invert the exclusivity info. ---@param pagenot string The entity whose exclusivity info to subtract from the main one's. ---@param dcom3j string Manual exclusivity info to override the fetched one with. ---@return number info An integer that holds the exclusivity information. local function getInfo(page, invert, pagenot, dcom3j) local info = 0

-- A piece of exclusivity information is a set of Boolean values, one -- for each platform. This is represented as bits of the `info` integer. -- Each platform (Desktop, Console, Old-gen console, Mobile, 3DS,	-- and Japanese console – "dcom3j") is assigned one bit, in this order. -- This means that, for instance, an `info` value of 1 would represent -- Desktop-only exclusivity ("d"): -- decimal 1 = binary 00000001 --                    tsj3mocd  -> "d" -- Similarly, an `info` value of 20 would represent Old-gen and 3DS exclusivity ("o3"): -- decimal 20 = binary 00010100 --                    tsj3mocd  -> "o3" -- See Module:Exclusive/data for a quick overview of all values.

-- This system allows using bitwise operations (https://en.wikipedia.org/wiki/Bitwise_operation) -- instead of the formerly used string processing, resulting in much lower script execution times.

-- get info about page if page then info = readFromDb(page) if invert then -- invert dcsom3t and set j=0 (always force-off Japanese console when inverting) info = bit32.band(bit32.bnot(info), 223) end if pagenot then -- exclude some versions, depending on pagenot local info_not = readFromDb(pagenot) info = bit32.band(info, bit32.bnot(info_not)) end

-- The "invert" and "pagenot" functionalities above utilize -- bit masking (https://en.wikipedia.org/wiki/Mask_(computing)). -- The following example demonstrates the operations: -- 1. assume info=203 ("dcsmt", binary 11001011) -- 2. invert: --   2a. not(11001011) = 00110100 ("o3j", the inverse of "dcsmt") --   2b. and(00110100, 11011111) = 00010100 ("o3", forced-off "j") -- 3. assume info_not=4 ("o", binary 00000100) --   3a. not(00000100) = 11111011 --   3b. and(00010100, 11111011) = 00010000 ("3") -- An initial exclusivity info of "dcm" was inverted to "o3", -- then "o" was subtracted from it, resulting in the final -- exclusivity information of "3". end

-- override if needed if dcom3j == nil or dcom3j == ':::::::' then return info else return override(info, dcom3j) end end

---Return an HTML span tag whose `class` attribute is set ---according to the exclusivity `info`. ---@param info number ---@param _small boolean Whether to add the "s" class, for small icons ---@return string local function eicons(info, _small) local class = 'eico' .. (_small and ' s' or '')

if bit32.btest(info, 32) then -- Japanese console is set, so simply display that -- ("j" is always alone or not set at all – "dcj", for instance, doesn't exist) class = class .. ' j'		local hovertext = l10n('text_j') return mw.text.tag('span', {class=class}, mw.text.tag('span', {title=hovertext}, '')) end

-- for each platform of dcom3, add the class and load the hovertext if the platform if set -- (e.g. for info=25 ("dm3"), append "i0 i3 i4" to the class and load the	-- "text_0", "text_3", and "text_4" l10n strings) local container = mw.html.create("span"):attr("class", class) local v = {} for i = 0, 7 do		if i ~= 5 and bit32.btest(info, 2^i) then container:tag("span"):addClass("i" .. i) v[#v+1] = l10n('text_' .. i)		end end local hovertext = mw.text.listToText(v, l10n('list_separator'), l10n('list_conjunction')) hovertext = hovertext .. contentLanguage:convertPlural(#v, l10n('version_plural_forms')) container:attr("title", hovertext) return tostring(container) end

---Check if the `infoToCheck` integer is a valid number for output. ---It is considered invalid if it represents an empty exclusivity ("") ---or a "full" exclusivity, i.e. all platforms being set ("dcsom3t" or "dcsom3jt"). ---@param infoToCheck number ---@return boolean local function infoIsInvalid(infoToCheck) return infoToCheck == 0 or infoToCheck == 223 or infoToCheck == 255 end

- -- main return object return {

-- for templates; get all exclusive info and set it in dplvars. -- parameters: $1 = pagename getInfo = function(frame) args_table = frame.args -- cache

local info = getInfo(getArg(1), getArg('invert'), getArg('pagenot')) if infoIsInvalid(info) then frame:callParserFunction{ name = '#dplvar:set', args = { 'ex_d', '', 'ex_c', '', 'ex_o', '', 'ex_m', '', 'ex_3', '', 'ex_j', '', 'ex_s', '', 'ex_t', '', 'ex_cached', 'y'		} } else frame:callParserFunction{ name = '#dplvar:set', args = { 'ex_d', bit32.btest(info, 2^0) and 'y' or '', 'ex_c', bit32.btest(info, 2^1) and 'y' or '', 'ex_o', bit32.btest(info, 2^2) and 'y' or '', 'ex_m', bit32.btest(info, 2^3) and 'y' or '', 'ex_3', bit32.btest(info, 2^4) and 'y' or '', 'ex_j', bit32.btest(info, 2^5) and 'y' or '', 'ex_s', bit32.btest(info, 2^6) and 'y' or '', 'ex_t', bit32.btest(info, 2^7) and 'y' or '', 'ex_cached', 'y'		} } end end,

-- for eicons = function(frame) args_table = parse(frame.args[1])-- cache

local info = getInfo(getArg('page'), getArg('invert'), getArg('pagenot'), getArg('dcom3j')) if infoIsInvalid(info) then return frame:expandTemplate{ title = 'error', args = { l10n('eicons_error_text'), l10n('eicons_error_cate'), from = 'Eicons' } } end lang = getArg('lang') -- set lang for l10n return eicons(info, getArg('small')) end,

-- simplified version of the eicons function above, for other modules such as Module:Item simpleEicons = function(page, language, small) local info = getInfo(page) if infoIsInvalid(info) then return '' end lang = language -- set lang for l10n return eicons(info, small) end,

}