Capil ka isi

Modul:topic list

Matan Wiktionary

Dokumentasi untuk modul ini dapat dibuat di Modul:topic list/doc

-- Imported from enwiktionary
-- Source: https://en.wiktionary.org/wiki/Module:topic list
-- License: CC BY-SA

local export = {}

local columns_module = "Module:columns"
local fun_is_callable_module = "Module:fun/isCallable"
local languages_module = "Module:languages"
local load_module = "Module:load"
local parameters_module = "Module:parameters"
local parameter_utilities_module = "Module:parameter utilities"
local parse_interface_module = "Module:parse interface"
local string_replacement_escape_module = "Module:string/replacementEscape"
local string_utilities_module = "Module:string utilities"
local title_exists_module = "Module:title/exists"
local utilities_module = "Module:utilities"

local concat = table.concat
local error = error
local insert = table.insert
local ipairs = ipairs
local pairs = pairs
local require = require
local tostring = tostring
local unpack = unpack or table.unpack -- Lua 5.2 compatibility

local html_create = mw.html.create

local function construct_param_mods(...)
	construct_param_mods = require(parameter_utilities_module).construct_param_mods
	return construct_param_mods(...)
end

local function construct_old_style_header(...)
	construct_old_style_header = require(columns_module).construct_old_style_header
	return construct_old_style_header(...)
end

local function format_categories(...)
	format_categories = require(utilities_module).format_categories
	return format_categories(...)
end

local function format_item(...)
	format_item = require(columns_module).format_item
	return format_item(...)
end

local function get_lang(...)
	get_lang = require(languages_module).getByCode
	return get_lang(...)
end

local function handle_display_from_or_topic_list(...)
	handle_display_from_or_topic_list = require(columns_module).handle_display_from_or_topic_list
	return handle_display_from_or_topic_list(...)
end

local function is_callable(...)
	is_callable = require(fun_is_callable_module)
	return is_callable(...)
end

local function load_data(...)
	load_data = require(load_module).load_data
	return load_data(...)
end

local function parse_inline_modifiers(...)
	parse_inline_modifiers = require(parse_interface_module).parse_inline_modifiers
	return parse_inline_modifiers(...)
end

local function parse_term_with_lang(...)
	parse_term_with_lang = require(parse_interface_module).parse_term_with_lang
	return parse_term_with_lang(...)
end

local function process_params(...)
	process_params = require(parameters_module).process
	return process_params(...)
end

local function replacement_escape(...)
	replacement_escape = require(string_replacement_escape_module)
	return replacement_escape(...)
end

local function split_on_comma(...)
	split_on_comma = require(parse_interface_module).split_on_comma
	return split_on_comma(...)
end

local function title_exists(...)
	title_exists = require(title_exists_module)
	return title_exists(...)
end

local function ucfirst(...)
	ucfirst = require(string_utilities_module).ucfirst
	return ucfirst(...)
end

local function umatch(...)
	umatch = require(string_utilities_module).match
	return umatch(...)
end

local force_cat = false -- for testing

local function letter_like_category(data)
	return ("Category:%s letters"):format(data.lang:getCanonicalName())
end

local function letter_like_appendix(data)
	local appendices = {}
	local alphabet_appendix = ("Appendix:%s alphabet"):format(data.lang:getCanonicalName())
	if title_exists(alphabet_appendix) then
		insert(appendices, ("[[%s|alphabet appendix]]"):format(alphabet_appendix))
	end
	local script_name = data.list_name:match("^(.* script) .*$")
	if not script_name then
		error(("Internal error: Can't pull out script name from list name '%s'"):format(data.list_name))
	end
	local script_appendix = "Appendix:" .. script_name
	if title_exists(script_appendix) then
		insert(appendices, ("[[%s|script appendix]]"):format(script_appendix))
	end
	if appendices[1] then
		return concat(appendices, ",")
	else
		return nil
	end
end

local letter_like_properties = {
	horiz = "comma",
	sort = false,
	cat = letter_like_category,
	appendix = letter_like_appendix,
	notr = true,
	allow_space_delim = true, -- allow space as a delimiter
	tilde_delim = "~", -- use a tilde without spaces around it
}

local function letter_name_category(data)
	local script_name = data.list_name:match("^(.*) script .*$")
	if not script_name then
		error(("Internal error: Can't pull out script name from list name '%s'"):format(data.list_name))
	end
	return ("%s letter names"):format(script_name)
end

local function calendar_month_adjectives_category(data)
	local calendar = data.list_name:match("^(.*) calendar month adjectives$")
	if not calendar then
		error(("Internal error: Can't pull out calendar type from list name '%s'"):format(data.list_name))
	end
	return calendar .. " calendar months"
end

local topic_list_properties = {
	{".* calendar months", {sort = false}},
	{".* calendar month adjectives", {sort = false, cat = calendar_month_adjectives_category}},
	{"compass point adjectives", {cat = "Compass points"}},
	{"compass point adverbs", {cat = "Compass points"}},
	{".* script letters", letter_like_properties},
	{".* script vowels", letter_like_properties},
	{".* script consonants", letter_like_properties},
	{".* script diacritics", letter_like_properties},
	{".* script digraphs", letter_like_properties},
	-- FIXME: We may need to be smarter, and use a regular columnar display in some cases (e.g. when translit is
	-- present)
	{".* script letter names", {horiz = "bullet", sort = false, cat = letter_name_category}},

	-- Geographic lists should not categorize. {{place}} does that, and may do it differently.
	{"administrative .* of .*", {nocat = true}},
	{"autonomous .* of .*", {nocat = true}},
	{"cantons of .*", {nocat = true}},
	{"capital cities of .*", {nocat = true}},
	{"continents", {nocat = true}},
	{"counties of .*", {nocat = true}},
	{"countries in .*", {appendix = "Appendix:Countries of the world", nocat = true}},
	{"districts of .*", {nocat = true}},
	{"divisions of .*", {nocat = true}},
	{"federal .* of .*", {nocat = true}},
	{"governorates of .*", {nocat = true}},
	{"macroregions of .*", {nocat = true}},
	{"prefectures of .*", {nocat = true}},
	{"provinces.* of .*", {nocat = true}},
	{"regions of .*", {nocat = true}},
	{"states.* of .*", {nocat = true}},
	{"territories of .*", {nocat = true}},
	{"union territories of .*", {nocat = true}},
	{"vilayets of .*", {nocat = true}},

	{"books of the.* Testament", {sort = false, cat = "Books of the Bible"}},
	-- FIXME: Use the following instead of the previous when we create 'Books of the Old Testament' and
	-- 'Books of the New Testament'
	--{"books of the Old Testament", {sort = false}},
	--{"books of the .*New Testament", {sort = false, cat = "Books of the New Testament"}},
	{"canids", {horiz = "bullet"}}, -- only 5 items on most lists
	{"Chinese earthly branches", {sort = false, cat = "Chinese earthly branches"}},
	{"Chinese heavenly stems", {sort = false, cat = "Chinese heavenly stems"}},
	{"Chinese sexagenary cycle terms", {sort = false, cat = "Chinese sexagenary cycle terms"}},
	{"days of the week", {horiz = "bullet", sort = false, appendix = "Appendix:Days of the week"}},
	{"dentistry location adjectives", {cat = "Dentistry"}},
	-- FIXME: Delete the following once we rename these categories to 'electromagnetic spectrum'
	{"electromagnetic radiation", {sort = false, cat = "Electromagnetic spectrum"}}, -- FIXME, add category
	{"electromagnetic spectrum", {sort = false}},
	-- FIXME: Delete the following once we rename these categories to 'terms for fingers'
	{"fingers", {sort = false, cat = "Terms for fingers"}},
	{"terms for fingers", {sort = false, cat = "Terms for fingers"}},
	{"fundamental interactions", {cat = "Physics"}},
	{"geological time units", {sort = false, cat = "Units of time,Geology"}},
	{"human anatomy location adjectives", {cat = "Medicine"}},
	{"Islamic prophets", {sort = false}},
	{"leptons", {sort = false, horiz = "bullet"}},
	{"antileptons", {sort = false, horiz = "bullet", cat = "Leptons"}},
	{"oceans", {horiz = "bullet"}},
	{"planets of the Solar System", {horiz = "bullet", sort = false}},
	{"quarks", {sort = false, horiz = "bullet"}},
	{"squarks", {sort = false, horiz = "bullet", cat = "Quarks"}},
	{"antiquarks", {sort = false, horiz = "bullet", cat = "Quarks"}},
	{"religions", {cat = "Religion"}}, -- FIXME, add and use category 'Religions'
	{"religious adherents", {cat = "Religion"}},
	{"religious texts", {cat = "Religion"}}, -- FIXME, add and use category 'Religious texts'
	{"seasons", {horiz = "bullet", sort = false}},
	{"sexual orientation adjectives", {cat = "Sexual orientations"}},
	{"taxonomic ranks", {sort = false}},
	{"times of day", {sort = false}},
	{"units of time", {sort = false}},
}

local function parse_topic_list_user_args(raw_user_args)
	local user_params = {
		nocat = {type = "boolean"},
		sortbase = {},
	}

	return process_params(raw_user_args, user_params)
end

local function add_topic_list_params(params)
	params["cat"] = true
	params["nocat"] = {type = "boolean"}
	params["enhypernym"] = true
	params["hypernym"] = true
	params["appendix"] = true
	params["pagename"] = true -- for testing of topic list
end

local function set_default_arguments(args, default_props)
	if default_props then
		for k, v in pairs(default_props) do
			if args[k] == nil then
				args[k] = v
			end
		end
	end
end

local function make_horiz_edit_button(topic_list_template)
	return tostring(html_create("small"):tag("sup")
		:wikitext(" [[[Special:EditPage/" .. topic_list_template .. "|edit]]]")
		:allDone()
	)
end

local function analyze_template_name(topic_list_template)
	-- Analyze template name for list name language and variant. The variant comes after the language code and a
	-- slash and may or may not be present. Cf. [[Template:list:days of the week/cim/Luserna]] (for the Luserna dialect
	-- of Cimbrian) and [[Template:list:days of the week/cim/13]] (for the Tredici Comuni dialect of Cimbrian).
	--
	-- Also compute default properties based on template name.
	local list_name_plus_lang = topic_list_template:gsub("^Template:", ""):gsub("^list:", "")
	local list_name, langcode_and_variant = list_name_plus_lang:match("^(.-)/(.*)$")
	local lang, variant
	if langcode_and_variant then
		local langcode
		langcode, variant = langcode_and_variant:match("^(.-)/(.*)$")
		langcode = langcode or langcode_and_variant
		lang = get_lang(langcode, nil, "allow etym")
		if not lang then
			error(("Unrecognized language code '%s' in topic list template name [[%s]]"):format(
				langcode, topic_list_template))
		end
	else
		error(("Can't parse language code out of topic list template name [[%s]]; it should be of the form " ..
			"'Template:list:LISTNAME/LANGCODE' or 'Template:list:LISTNAME/LANGCODE/VARIETY'"):format(
			topic_list_template))
	end

	local default_props
	for _, pattern_and_props in ipairs(topic_list_properties) do
		local pattern, props = unpack(pattern_and_props)
		if umatch(list_name, "^" .. pattern .. "$") then
			default_props = props
			break
		end
	end
	if default_props then
		-- Safest to make a copy of the default props in case we modify it, as we do when one of the values is a
		-- function, in case we are called twice in the same Scribunto invocation (not common but possible I think).
		local default_props_copy = {}
		for k, v in pairs(default_props) do
			if is_callable(v) then
				default_props_copy[k] = v {
					topic_list_template = topic_list_template,
					list_name = list_name,
					lang = lang,
					variant = variant,
				}
			else
				default_props_copy[k] = v
			end
		end
		default_props = default_props_copy
	end

	return list_name, lang, variant, default_props
end

-- FIXME: This needs to be implemented properly in [[Module:links]].
local function get_left_side_link(link)
	local left = link:match("^%[%[([^%[%]|]+)|[^%[%]|]+%]%]$")
	if left then
		return left
	end
	local single_part = link:match("^%[%[([^%[%]|]+)%]%]$")
	if single_part then
		return single_part
	end
	if not link:match("%[%[") then
		return link
	end
	return nil
end

local term_param_mods
local function get_term_param_mods()
	term_param_mods, get_term_param_mods = construct_param_mods{
		{group = "link"}, -- sc has separate_no_index = true; that's the only one
		-- It makes no sense to have overall l=, ll=, q= or qq= params for columnar display.
		{group = {"ref", "l", "q"}, require_index = true},
	}, nil
	return term_param_mods
end

local function parse_term_with_delimiters(data)
	local term, paramname, lang, sc, pagename = data.term, data.paramname, data.lang, data.sc, data.pagename
	local function generate_obj(term, parse_err)
		local actual_term, termlang = parse_term_with_lang{
			term = term,
			parse_err = parse_err,
			paramname = paramname,
		}
		return {
			term = actual_term ~= "" and actual_term or nil,
			lang = termlang or lang,
		}
	end
	local items = parse_inline_modifiers(term, {
		paramname = paramname,
		param_mods = term_param_mods or get_term_param_mods(),
		generate_obj = generate_obj,
		splitchar = "[,~]",
		preserve_splitchar = true,
		outer_container = {},
	})
	local item_is_page = false
	for i, item in ipairs(items.terms) do
		if i == 1 then
			item.separator = nil
		elseif item.delimiter == "~" then
			item.separator = data.tilde_delim or " ~ "
		else
			item.separator = data.comma_delim or ", "
		end
		-- Do this afterwards rather than in generate_obj() or use of <sc:...> will trigger an error
		-- because the modifier is already set.
		if not item.sc and sc then
			item.sc = sc
		end
		if data.notr then
			item.tr = "-"
		end
		if item.term and pagename then
			local left_side = get_left_side_link(item.term)
			if left_side and left_side == pagename then
				item_is_page = true
			end
		end
	end
	return items, item_is_page
end

local function get_title_and_formatted_cats(args, lang, sc, topic_list_data)
	local title = args.title
	local formatted_cats
	local cats
	local default_title = topic_list_data.list_name
	local user_args = topic_list_data.user_args
	local nocat = args.nocat or user_args.nocat 
	local fulllangcode = lang:getFullCode()
	if nocat then
		-- don't generate categories for the title
	elseif not args.cat then
		local default_cat = ucfirst(default_title)
		local cat_title = mw.title.new(("Category:%s"):format(default_cat))
		if cat_title and cat_title.exists then
			cats = {fulllangcode .. ":" .. default_cat}
		end
	elseif args.cat ~= "-" then
		cats = split_on_comma(args.cat)
		for i, cat in ipairs(cats) do
			if cat:find("^Category:") then
				cats[i] = cat:gsub("^Category:", "")
			else
				cats[i] = fulllangcode .. ":" .. cats[i]
			end
		end
	end
	local hypernym_is_page
	if not title then
		local titleparts = {}
		local function ins(txt)
			insert(titleparts, txt)
		end
		local enhypernym
		if args.enhypernym then
			enhypernym = args.enhypernym:gsub("%+", replacement_escape(default_title))
		else
			enhypernym = default_title
			if topic_list_data.variant then
				enhypernym = ("%s (%s)"):format(enhypernym, topic_list_data.variant)
			end
		end
		if cats and not enhypernym:find("%[%[") then
			ins(("[[:Category:%s|%s]]"):format(cats[1], enhypernym))
		else
			ins(enhypernym)
		end
		if args.hypernym then
			local lang_hypernyms
			local pagename = args.pagename or load_data("Module:headword/data").pagename
			lang_hypernyms, hypernym_is_page = parse_term_with_delimiters {
				term = args.hypernym,
				paramname = "hypernym",
				lang = lang,
				sc = sc,
				pagename = pagename,
				-- Don't set notr= here; we still want to transliterate the hypernym if given
			}
			local formatted_hypernyms = format_item(lang_hypernyms, {lang = lang}, "bold")
			ins(": " .. formatted_hypernyms)
		end
		if args.appendix then
			local appendices = split_on_comma(args.appendix)
			for i, appendix in ipairs(appendices) do
				if not appendix:find("%[%[") then
					appendices[i] = ("[[%s|appendix]]"):format(appendix)
				end
			end
			ins((" (<small>%s</small>)"):format(concat(appendices, ", ")))
		end
		title = concat(titleparts)
	end

	if not args.horiz and not topic_list_data.suppress_edit_button then
		-- append edit button to title
		local edit_link = html_create("span")
			:addClass("list-switcher-edit")
			:wikitext("[[Special:Edit/" .. topic_list_data.topic_list_template .. "|edit]]")
		title = title .. tostring(edit_link)
	end

	if cats and not nocat and not hypernym_is_page then
		formatted_cats = format_categories(cats, lang, nil, user_args.sortbase, force_cat)
	end
	return title, formatted_cats
end

--[==[
This implements topic lists. A given topic list template must directly invoke this function rather than
going through a wrapping template. A sample template implementation (e.g. for {{tl|list:continents/sw}}) is
{
{{#invoke:topic list|show|sw
|hypernym=[[bara|mabara]]
|Afrika<t:Africa>
|Antaktika~Antaktiki<t:Antarctica>
|Asia<t:Asia>
|Ulaya,Uropa<t:Europe>
|Amerika ya Kaskazini<t:North America>
|Amerika ya Kusini<t:South America>
|Australia<t:Australia>
}}
}

The syntax of the params is largely the same as for {{tl|col}}, but the following additional params supported:
* {{para|cat}}: Comma-separated list of categories to add the page to. The categories should specify bare topic
  categories without the preceding language code, e.g. `Islamic calendar months` for a category
  `LANGCODE:Islamic calendar months`. If you need to specify a different type of category, prefix the full category name
  with `Category:`. There must not be any spaces after the comma delimiter for it to be recognized as such. By default,
  the category comes from the template name, minus the initial `list:` and anything starting with a slash, and with the
  first letter capitalized; but only if the top-level language-agnostic category exists. Hence a template like
  {{tl|list:prefectures of Japan/ja}} will automatically categorize into [[:Category:ja:Prefectures of Japan]] (and
  likewise {{tl|list:prefectures of Japan/sq}} will still categorize into [[:Category:sq:Prefectures of Japan]] whether
  or not it exists, because the top-level [[:Category:Prefectures of Japan]] exists), but a template like
  {{tl|list:human anatomy direction adjectives/en}} will not categorize anywhere by default because there is no
  top-level [[:Category:Human anatomy direction adjectives]]. Use {{para|cat|-}} to disable categorization. Note that no
  categorization takes place if {{para|nocat|1}} is specified in the invocation of the list template or if the page is
  the same as any of the pages linked to by the language-specific hypernym(s) given in {{para|hypernym}}.
* {{para|hypernym}}: The language-specific plural form of the hypernym category (e.g. in the above example, the word for
  "continents" in Swahili). Generally this should be linked to the corresponding singular, if they are different. If
  specified, it is displayed after the English hypernym, following a colon. This parameter can be omitted to not display
  a hypernym, and you can also specify multiple comma-separated or tilde-separated hypernyms with inline modifiers
  attached to each one (essentially, following the format of a given item in {{tl|col}}).
* {{para|enhypernym}}: The English-language hypernym, e.g. `continents` in the above example. If omitted, this is
  derived from the template name by chopping off the initial `list:` and anything starting with a slash. This will be
  hyperlinked to the first category that the page categorizes into, if such a category exists.
* {{para|appendix}}: One or more comma-separated appendices to display after the hypernym, in parens. The appendix
  should be a full pagename including the namespace `Appendix:`, or a two-part link giving a pagename and display form.
  If a link is not specified, the display form will be `appendix`.
* {{para|pagename}}: Override the pagename, which should normally be a template of the form
  `Template:list:<var>list name</var>/<var>langcode</var>` or
  `Template:list:<var>list name</var>/<var>langcode</var>/<var>variant</var>`. The list name and language code are
  parsed out of the pagename and used as the default title and language, and various other defaults are set based on the
  list name.
]==]
function export.show(frame)
	local raw_item_args = frame.args
	local frame_parent = frame:getParent()
	local raw_user_args = frame_parent.args
	local topic_list_template = raw_item_args.pagename or frame_parent:getTitle()

	local user_args = parse_topic_list_user_args(raw_user_args)

	local list_name, lang, variant, default_props = analyze_template_name(topic_list_template)

	return handle_display_from_or_topic_list(
		{minrows = 2, sort = true, collapse = true, lang = lang}, raw_item_args, {
			topic_list_template = topic_list_template,
			list_name = list_name,
			variant = variant,
			set_default_arguments = function(args)
				set_default_arguments(args, default_props)
			end,
			get_title_and_formatted_cats = get_title_and_formatted_cats,
			add_topic_list_params = add_topic_list_params,
			make_horiz_edit_button = make_horiz_edit_button,
			user_args = user_args,
		}
	)
end

local function parse_and_format_direction(paramname, args, lang, sc, notr)
	local term = args[paramname]
	if not term then
		return ""
	end
	local terms = parse_term_with_delimiters {
		term = term,
		paramname = paramname,
		lang = lang,
		sc = sc,
		comma_delim = "<br />",
		notr = notr,
	}
	return format_item(terms, {lang = lang})
end

function export.parse_directions_and_draw_compass(args, lang, sc, notr)
	return '{| frame=void border=0 rules=none style="background:var(--wikt-palette-paleblue, #f9f9f9);"' ..
		'\n| style="text-align: left"   | ' ..
		parse_and_format_direction("nw", args, lang, sc, notr) ..
		'\n| style="text-align: center" | ' ..
		parse_and_format_direction("n", args, lang, sc, notr) ..
		'\n| style="text-align: right"  | ' ..
		parse_and_format_direction("ne", args, lang, sc, notr) ..
		"\n|-" ..
		'\n| style="text-align: left"   | ' ..
		parse_and_format_direction("w", args, lang, sc, notr) ..
		"\n| [[File:Compass rose simple plain.svg|upright=0.31]] " ..
		'\n| style="text-align: right"  | ' ..
		parse_and_format_direction("e", args, lang, sc, notr) ..
		"\n|-" ..
		'\n| style="text-align: left"   | ' ..
		parse_and_format_direction("sw", args, lang, sc, notr) ..
		'\n| style="text-align: center" | ' ..
		parse_and_format_direction("s", args, lang, sc, notr) ..
		'\n| style="text-align: right"  | ' ..
		parse_and_format_direction("se", args, lang, sc, notr) ..
		"\n|}"
end
	
function export.compass_plain(frame)
	local parent_args = frame:getParent().args

	local params = {
		[1] = {type = "language"},
		["sc"] = {type = "script"},
		["notr"] = {type = "boolean"},

		["n"] = true,
		["s"] = true,
		["e"] = true,
		["w"] = true,
		["ne"] = true,
		["nw"] = true,
		["se"] = true,
		["sw"] = true,
	}

	local args = process_params(parent_args, params)

	return export.parse_directions_and_draw_compass(args, args[1], args.sc, args.notr)
end

function export.compass(frame)
	local raw_item_args = frame.args
	local frame_parent = frame:getParent()
	local raw_user_args = frame_parent.args
	local topic_list_template = raw_item_args.pagename or frame_parent:getTitle()

	local user_args = parse_topic_list_user_args(raw_user_args)

	local list_name, lang, variant, default_props = analyze_template_name(topic_list_template)

	local params = {
		["lang"] = {type = "language"},
		["sc"] = {type = "script"},
		["notr"] = {type = "boolean"},
		["title"] = true,

		["n"] = true,
		["s"] = true,
		["e"] = true,
		["w"] = true,
		["ne"] = true,
		["nw"] = true,
		["se"] = true,
		["sw"] = true,
	}
	add_topic_list_params(params)

	local args = process_params(raw_item_args, params)

	set_default_arguments(args, default_props)

	local title, formatted_cats = get_title_and_formatted_cats(args, args.lang or lang, args.sc, {
		topic_list_template = topic_list_template,
		list_name = list_name,
		variant = variant,
		user_args = user_args,
		suppress_edit_button = true,
	})

	return construct_old_style_header(title, "horiz") ..
		make_horiz_edit_button(topic_list_template) ..
		"\n" ..
		export.parse_directions_and_draw_compass(args, args.lang or lang, args.sc, args.notr) ..
		(formatted_cats or "")
end

return export