Module:Tag list generator

From Minecraft Wiki
Jump to navigation Jump to search
Documentation[view] [edit] [history] [purge]Jump to code ↴

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.

[view] [edit] [history] [purge]The above documentation is transcluded from Module:Tag list generator/doc.
-- 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