Module:Tag list generator
Usage
[edit source]The module "Tag list generator" has to goal to provide various function to generate content relative to tag.
used_node_list()
[edit source]Build a treview node list with the posibility to list the tags that use the one create. See Template:Tag_list.
node_list()
[edit source]Similar to used_node_list(), but without supporting the parameter type for generate the "Used in the tags" line before.
inline_list()
[edit source]Build a text of tags links. The first argument is the targed page for the link. See Template:Tag_link.
used_list()
[edit source]Build a text of tags links "Used in the tags." that list all the tags that use a named one. The first argument is the type of tag, the second is the name of the tag to search, and the optional third the page to target for the links. If the tag is not reused, return a empty text.
The data to manage the auto-generation can be found here Module:Tag_list_generator/data.json.
-- string that can be localized
-- be careful to keep the number of %s
local text_values = '%s values'
local text_empty_list = 'Empty in vanilla.'
local text_used_in_singular = 'Used in the %s tag.'
local text_used_in_plurals = 'Used in the %s tags.'
local template_experimental = 'OnlyExperimental'
local template_until = 'until'
local template_upcoming = 'upcoming'
local module_delimited_tag = 'Module:Delimited tag' -- the one use in {{code}} template
local msg_error_name = 'Name is not present or empty.'
local msg_error_rename_empty = 'The new name is empty.'
local msg_error_rename_version = 'The tag is renamed but no until/upcoming versions are provided.'
local msg_error_sort = 'The sort key "%s" is invalid.'
local msg_error_type = 'Type is not present or empty.'
local msg_error_type_invalid = 'The type "%s" is invalid.'
local msg_error_used_missing_type = 'No tag of the type "%s" is reused.'
local msg_error_used_missing_tag = 'The tag "%s" is not reused.'
-- The keys as sort by priority. No case sensitive.
local key_until = {'until'}
local key_upcoming = {'upcoming'}
local key_rename = {'rename'}
local key_experimental = {'OnlyExperimental', 'experimental', 'experiment'}
local key_collapse = {'collapse', 'c'}
local key_sort = {'sort', 's'}
local key_page_link = {'page_link', 'page', 'link'}
local key_discret = {'discret'}
local key_used_type = {'used_type', 'type'}
local key_raise_error = {'raise_error', 'error'}
local key_sort_asc = {'ascendant', 'asc', 'a'}
local key_sort_desc = {'descendant', 'desc', 'd'}
local key_sort_none = {'none', 'n'}
-- end localized string
--
local function show_error(message)
return '<p><span class="error"><strong>Tag list error</strong>: '.. tostring(message) ..'</span></p>'
end
local function string_split(text, sep)
-- split a string with the provided characters (use preferly only one)
if not sep then
sep = "%s"
end
local rslt = {}
for s in string.gmatch(text, '([^'..sep..']+)') do
table.insert(rslt, s)
end
return rslt
end
local function last_of(_table)
-- retrive the last entry of a table
return _table[#_table]
end
local function get_arg_multiple(args, argument_keys)
-- get the arg from a list of key to search
for i, v in ipairs(argument_keys) do
local t = args[string.lower(v)]
if t then
return mw.text.trim(t)
end
end
return nil
end
local function parse_state_arg(value, states)
-- test the different state of a values
if not value or value == '' then
return states[1]
end
value = string.lower(value)
for rslt, tbl in pairs(states) do
if type(tbl) == 'table' then
for i, test in ipairs(tbl) do
if value == string.lower(test) then
return rslt
end
end
end
end
return states[2]
end
local function parse_arg_sort(value)
-- test the different state of error
return parse_state_arg(value, {
[1]= 'asc',
['asc']= key_sort_asc,
['desc']= key_sort_desc,
['none']= key_sort_none,
})
end
local function namespace(text)
-- uniform a string to a namespaced value
if not text then
return nil
end
text = mw.text.trim(text)
local is_tag, value, trail
-- split trail text
local idx, _
idx, _ = string.find(text, '[^%w#:._/\\-]')
if idx then
trail = string.sub(text, idx)
value = string.sub(text, 1, idx-1)
else
trail = ''
value = text
end
if string.len(value) == 0 then
return false, '', trail
end
-- lower case and uniform path separator
value = string.lower(value)
value = string.gsub(value, '\\', '/')
-- test if is a tag
is_tag = string.sub(value, 1, 1) == '#'
-- add 'minecraft' if no namespace specified
if not string.find(value, ':') then
if is_tag then
value = '#minecraft:'.. string.sub(value, 2)
else
value = 'minecraft:'.. value
end
end
return is_tag, value, trail
end
local function template(name, args)
-- get the output result of a {{Template}}
return mw.getCurrentFrame():expandTemplate{title=name, args=args}
end
local function load_data(page)
-- load a json data assosiated to a page
return mw.loadJsonData(page)
end
--- builder functions
local function build_node_header(
name, name_trail,
new_name, new_name_trail,
value_until, value_upcoming,
experimental,
num_collapse, entries
)
-- build the node list header
local rslt = {}
-- add the OnlyExperimental element
if experimental then
experimental = template(template_experimental, {experimental})
else
experimental = ''
end
if not new_name then
-- normal name
name = template('nbt', {'list', name})
if value_until then
value_until = template(template_until, {value_until})
else
value_until = ''
end
if value_upcoming then
value_upcoming = template(template_upcoming, {value_upcoming})
else
value_upcoming = ''
end
table.insert(rslt, name .. value_upcoming .. value_until .. experimental .. name_trail)
else
-- renamed tag
local name_until = template('nbt', {'list', name}) .. template(template_until, {value_until})
local name_upcoming = template('nbt', {'list', new_name}) .. template(template_upcoming, {value_upcoming})
value_until = name_until .. name_trail
value_upcoming = name_upcoming .. experimental .. new_name_trail
table.insert(rslt, value_until .. '<br>' .. value_upcoming)
end
-- append the numbers of entries
table.insert(rslt, "''(".. string.format(text_values, #entries) ..")''")
-- build the collapsible
local collapsible = '<div class="mw-collapsible'
if (num_collapse == 0) or (num_collapse > 0 and #entries > num_collapse) then
collapsible = collapsible .. ' mw-collapsed'
end
collapsible = collapsible .. '">'
table.insert(rslt, collapsible)
-- output the result
return table.concat(rslt, ' ')
end
local function build_tag_link(page_link, discret, value)
-- build a link for a tag
-- value is a string without the # prefix
local link, text, idx, _
-- syntax the value
text = '#'.. value
-- remove the namespace
idx, _ = string.find(value, ':')
if idx then
link = page_link .. '#' .. string.sub(value, idx+1)
else
link = page_link .. '#' .. value
end
-- build and output the link
return '<code>[[' .. link .. '|' .. text .. ']]</code>'
end
local function build_node_list(page_link, entries)
-- build the node list content
if #entries == 0 then
return "<ul><li>''".. text_empty_list .."''</li></ul>"
end
local is_tag, value, trail
local link, text
local rslt = {}
table.insert(rslt, '<ul class="mw-collapsible-content">')
for i, entry in ipairs(entries) do
is_tag, value, trail = unpack(entry)
if is_tag then
-- convert tag entry to discret link
text = build_tag_link(page_link, true, string.sub(value, 2))
else
-- format of value entry
text = '<code>'.. value ..'</code>'
end
-- append the list item
table.insert(rslt, '<li>'.. text .. trail .. '</li>')
end
table.insert(rslt, '</ul>')
return table.concat(rslt, '\n')
end
local function build_inline_tag_list(page_link, discret, values)
-- build a inline list of tags link
-- values is a table of string without the # prefix
-- use another modul to build de list (Module:Delimited tag)
-- build the arguments to pass
local args = {}
args['tag'] = ''
args['delimiter'] = 'and'
for i, v in ipairs(values) do
table.insert(args, build_tag_link(page_link, discret, v))
end
-- generate the list
local rslt, _
rslt = require(module_delimited_tag).output(args)
rslt, _ = string.gsub(rslt, '</?>', '')
return rslt
end
local function build_used_tag_list(used_type, value, page_link, discret, raise_error)
-- build a inline list of tags link that use another one
-- value is a string without the # prefix
-- remove the namespace
local idx, _ = string.find(value, ':')
if idx then
value = string.sub(value, idx+1)
end
used_type = string.lower(used_type)
-- load the data assosiated to this module
local data = load_data(mw.getCurrentFrame():getTitle() .. '/data.json')
data = data[used_type]
if not data then
-- target type is not present
if raise_error then
return show_error(string.format(msg_error_used_missing_type, value))
else
return nil
end
end
data = data[value]
if not data then
-- target tag is not present
if raise_error then
return show_error(string.format(msg_error_used_missing_tag, value))
else
return nil
end
end
-- retrive the values list for this tag
local values = {}
for i, v in pairs(data) do
table.insert(values, v)
end
table.sort(values)
if #values == 0 then
-- target tag is not present
if raise_error then
return show_error(string.format(msg_error_used_missing_tag, value))
else
return nil
end
end
-- build the inline list, with text 'Used in the tags.'
local text
if #values == 1 then
text = text_used_in_singular
else
text = text_used_in_plurals
end
return string.format(text, build_inline_tag_list(page_link, discret, values))
end
-- public functions
local p = {}
function p.used_node_list(f)
-- build a nbt node list,
-- with a line of tags link that use this one before, if so
local args = f
if f == mw.getCurrentFrame() then
args = f:getParent().args
end
local used_list = get_arg_multiple(args, key_used_type)
if used_list then
-- build the used list only if a used_type parameter is present
local used_args = {}
for k, v in pairs(args) do
used_args[k] = v
end
used_args[1] = used_list -- used_type
used_args[2] = args[1] -- tag name
used_args[3] = get_arg_multiple(args, key_page_link) -- page_link
used_args[last_of(key_discret)] = 1 -- discret
used_list = p.used_list(used_args)
if used_list then
used_list = used_list .. '\n'
end
end
if not used_list then
used_list = ''
end
-- build the node list
return used_list .. p.node_list(args)
end
function p.node_list(f)
-- build a nbt node list
local args = f
if f == mw.getCurrentFrame() then
args = f:getParent().args
end
for k, v in pairs(args) do
args[string.lower(k)] = v
end
local is_tag, value, trail
local name, name_trail
local entries = {}
-- iter the positional arguments
for i, v in ipairs(args) do
is_tag, value, trail = namespace(v)
if i == 1 then
-- retrive the parameter: name
if not value or string.len(value) == 0 then
return show_error(msg_error_name)
end
name = value
name_trail = trail
if not is_tag then
name = '#'.. name
end
end
if i > 1 and value and string.len(value) > 0 then
-- retrive and filter the entries
table.insert(entries, {is_tag, value, trail})
end
end
if not name then
-- safe guard if 0 arguments
return show_error(msg_error_name)
end
-- retrive the parameter: rename
is_tag, value, trail = namespace(get_arg_multiple(args, key_rename))
if value and string.len(value) == 0 then
return show_error(msg_error_rename_empty)
end
local new_name, new_name_trail
new_name = value
new_name_trail = trail
if new_name and not is_tag then
new_name = '#'.. new_name
end
-- retrive the parameter: until/upcoming
local value_until = get_arg_multiple(args, key_until)
local value_upcoming = get_arg_multiple(args, key_upcoming)
if new_name then
if not value_until and not value_upcoming then
return show_error(msg_error_rename_version)
end
-- make sure that both are filled if rename
if not value_upcoming then
value_upcoming = value_until
end
if not value_until then
value_until = value_upcoming
end
end
-- retrive the parameter: experimental
local experimental = get_arg_multiple(args, key_experimental)
-- retrive the parameter: page_link
local page_link = get_arg_multiple(args, key_page_link)
if not page_link then
page_link = ''
end
-- retrive the parameter: sort
local _value_sort = get_arg_multiple(args, key_sort)
local value_sort = parse_arg_sort(_value_sort)
if not value_sort then
return show_error(string.format(msg_error_sort, _value_sort))
end
-- retrive the parameter: collapse
local num_collapse = get_arg_multiple(args, key_collapse)
num_collapse = tonumber(num_collapse)
if not num_collapse then
num_collapse = 20
end
if num_collapse < 0 then
num_collapse = -1
end
-- unduplicate and sort the entries
if value_sort ~= 'none' then
local unique = {}
for i, entry in ipairs(entries) do
is_tag, value, trail = unpack(entry)
unique[value] = entry
end
entries = {}
for k, v in pairs(unique) do
table.insert(entries, v)
end
if value_sort == 'asc' then
table.sort(entries, function(a,b) return a[2] < b[2] end)
else
table.sort(entries, function(a,b) return a[2] > b[2] end)
end
end
-- build the node list
local rslt = {
'<div class="treeview">',
'<ul>',
'<li>',
build_node_header(
name, name_trail,
new_name, new_name_trail,
value_until, value_upcoming,
experimental,
num_collapse, entries
),
build_node_list(page_link, entries),
'</div>',
'</li>',
'</ul>',
'</div>',
}
return table.concat(rslt, '\n')
end
function p.inline_list(f)
-- build a inline list of tags link
local args = f
if f == mw.getCurrentFrame() then
args = f:getParent().args
-- overide the first argument if {{#invoke}} has give one
if f.args[1] then
args[1] = f.args[1]
end
end
for k, v in pairs(args) do
args[string.lower(k)] = v
end
-- retrive the parameter: page_link
local page_link = mw.text.trim(args[1])
-- iter the positional arguments
local values = {}
for i, v in ipairs(args) do
if i > 1 then
table.insert(values, v)
end
end
-- retrive the parameter: discret
local discret = get_arg_multiple(args, key_discret) ~= nil
return build_inline_tag_list(page_link, discret, values)
end
function p.used_list(f)
-- build a inline list of tags link that use another
local args = f
if f == mw.getCurrentFrame() then
args = f:getParent().args
-- if template has no third argument (page_link)
-- try to use the first argument of {{#invoke}}
if not args[3] then
args[3] = f.args[1]
end
end
for k, v in pairs(args) do
args[string.lower(k)] = v
end
-- retrive the parameter: used_type
local used_type = args[1]
if not used_type then
return show_error(msg_error_type)
end
used_type = mw.text.trim(used_type)
-- retrive the parameter: name
local name = args[2]
if not name then
return show_error(msg_error_name)
end
name = mw.text.trim(name)
if string.sub(name, 1, 1) == '#' then
name = string.sub(name, 2)
end
-- retrive the parameter: page_link
local page_link = args[3]
if not page_link then
page_link = ''
end
-- retrive the parameter: discret
local discret = get_arg_multiple(args, key_discret) ~= nil
-- retrive the parameter: raise_error
local raise_error = get_arg_multiple(args, key_raise_error) ~= nil
return build_used_tag_list(used_type, name, page_link, discret, raise_error)
end
p.main = p.used_node_list
return p