Module:Sandbox/Outrowed/Cite book

Jump to navigation Jump to search
Documentation[create] [refresh]
This module has no documentation. If you know how to use this module, please create it.
local p = {}

---Parse template variadic named parameters specifed by the "prefix"
---* Variadic named parameters is defined as "argN", where "arg" is parameter name, and "N" is a number
---* To specify the parameter name, "prefix" argument is provided
---* It returns a list of "prefix"-matched values, ordered ascending by "N"
---* For example, passing `{ name1: value1, name2: value2, name3: value3 }` produces `{ value1, value2, value3 }`
---@param args table<string, string>
---@param prefix string?
---@return table<number, string>
local function parse_variadic(args, prefix)
	local list = {}
	local prefix = prefix or ""

	if args[prefix] then
		list[1] = args[prefix]
	end

	local size = 0

	for key, value in pairs(args) do
		local key_match = "^" .. mw.ustring.gsub(prefix, "%-", "%%-") .. "(%d+)"
		local index = tonumber(mw.ustring.match(key, key_match))

		if index then
			list[index] = tostring(value)
			size = size + 1
		end
	end

	setmetatable(list, { __size = size })

	return list
end

---@alias FullName { first: string; last: string; } contain a person's first and last name

---@param args table<string, string>
---@param prefix string?
---@return table<number, FullName>
local function parse_full_name_list(args, prefix)
	local prefix = prefix or ""
	local first_names = parse_variadic(args, prefix .. "first")
	local last_names = parse_variadic(args, prefix .. "last")
	local size = 0
	local name_list = {}

	for idx, first_name in ipairs(first_names) do
		name_list[idx] = {
			first = first_name,
			last = last_names[idx]
		}
		size = size + 1
	end

	setmetatable(name_list, { __size = size })

	return name_list
end

---Parse name list table to a list of surname-first formatted strings
---* Surname-first formatted string specifically defined as "<last_name>, <first_name>;"
---@param name_list table<number, FullName>
---@return string
local function parse_surnamefs_str(name_list)
	local name_str_list = {}

	for _, val in ipairs(name_list) do
		table.insert(name_str_list, val.last .. ", " .. val.first)
	end
	
	return table.concat(name_str_list, "; ")
end

---Remove dots trail at the end of a string
---@param str string
---@return string
local function remove_dots_trail(str)
	return mw.ustring.gsub(str, "[.]+$", "")
end

---Test if a value is nil or "empty"
---* If a value is table, then test if its "empty" by counting its items, otherwise return true
---* If a value is something else, then test if its nil, otherwise return true
---@param value any
---@return boolean
local function is_available(value)
	if value == nil then
		return false
	elseif type(value) == "table" then
		if getmetatable(value).__size > 0 then
			return true
		end
		
		local count = 0

		for _, _ in pairs(value) do
			count = count + 1
		end

		return count > 0
	else
		return true
	end
end

---Format an external string, like `[<url> <name>]`
---@param url string
---@param name string
---@return string
local function format_external_link(url, name)
	return "[" .. url .. " " .. name .. "]"
end

function p.main(args) -- takes a table as argument	
	--[[ Book description ]]--
	local title = args.title
	local date = args.date
	local chapter = '"' .. args.chapter .. '"'
	local isbn = args.isbn
	
	--[[ Book credits ]]--
	local author_list = parse_full_name_list(args)
	local editor_list = parse_full_name_list(args, "editor-")
	local publisher_list = parse_variadic(args, "publisher")
	local author_link_list = parse_variadic(args, "author-link")
	
	--[[ Citing ]]--
	local chapter_url = args["chapter-url"]
	local url = args.url
	local access_date = args["access-date"]
	local pages = args.pages
	local page = args.page

	--[[ Linking ]]--
	if chapter_url then
		chapter = format_external_link(chapter_url, chapter)
	end

	if url then
		title = format_external_link(url, title)
	end

	--[[ Output ]]--
	-- Citation template according to Wikipedia's {{Cite book}} and [[Help:Citation Style 1]]

	-- Title
	local output = "''" .. title .. "''" .. "."
	
	-- Prefix date
	local creditedDate = ""

	if date then
		creditedDate = " (" .. date .. ")"
	end

	-- Prefix
	if is_available(author_list) and is_available(editor_list) then
		local authors_str = parse_surnamefs_str(author_list)
		local editors_str = "In " .. parse_surnamefs_str(editor_list)

		if getmetatable(editor_list).__size > 1 then
			editors_str = editors_str .. ", (eds.)"
		else
			editors_str = editors_str .. ", (ed.)"
		end

		output = authors_str .. creditedDate .. ". " .. editors_str .. ". " .. output
	elseif is_available(author_list) and not is_available(editor_list) then
		local authors_str = parse_surnamefs_str(author_list)

		output = authors_str .. creditedDate .. ". " .. output
	elseif is_available(editor_list) and not is_available(author_list) then
		local editors_str = parse_surnamefs_str(editor_list)

		if chapter then
			output = chapter .. ". " .. output
		end

		if getmetatable(editor_list).__size > 1 then
			editors_str = editors_str .. ", eds."
		else
			editors_str = editors_str .. ", ed."
		end

		if date then
			editors_str = editors_str .. " (" .. date .. ")."
		end

		output = editors_str .. " " .. output
	else
		if chapter then
			output = chapter .. ". " .. output
		end
	end

	-- Postfix
	if is_available(publisher_list) then
		output = output .. " "
			.. remove_dots_trail(table.concat(publisher_list, ", "))
			.. "."
	end
	
	local credited = is_available(author_list) or is_available(editor_list)
	if date and not credited then
		output = output .. " " .. date .. "."
	end
	
	if pages then
		output = output .. " " .. "pp. " .. pages .. "."
	elseif page then
		output = output .. " " .. "p. " .. page .. "."
	end

	if isbn then
		output = output .. " " .. "[[w:ISBN|ISBN]] " .. "[[w:Special:BookSources/" .. isbn .. "|" .. isbn .. "]]" .. "."
	end

	return output
end

function p.call(frame) -- for using {{#invoke}}
	local args = frame.args
	
	return p.main(args)
end

function p.template(frame) -- for template page
	local args = frame:getParent().args

	return p.main(args)
end

return p