Module:grc-decl/table

From Wiktionary, the free dictionary
Jump to navigation Jump to search

This module needs documentation.
Please document this module by describing its purpose and usage on the documentation page.

local path = 'Module:grc-decl'
local headers = mw.loadData(path .. '/decl/classes').headers

local str_find = string.find
local str_match = string.match
local str_gsub = string.gsub
local ustring_gsub = mw.ustring.gsub
local Array = require 'Module:array'

-- Commas or slashes in the cell for a particular form are converted to this.
local form_separator = ' / '

local export = {}

--[[
	Basic letters with and without diacritics, plus digamma and combining
	diacritics.
]]
local word_characters = mw.loadData('Module:grc-utilities/data').word_characters

-- displayed in cell that has no form in it
local empty_cell = '—'

local nonAttic_note = 'Dialects other than Attic are not well attested. ' ..
	'Some forms may be based on conjecture. Use with caution.'
local Attic_note = 'This table gives Attic inflectional endings. ' ..
	'For declension in other dialects, see [[Appendix:Ancient Greek dialectal declension]].'

local case_names = { 'Nominative', 'Genitive', 'Dative', 'Accusative', 'Vocative' }

local function form_is_empty(form)
	return not form or form == '' or form == '-' or form == '—'
end

-- Use the fields in a table to fill out template parameter syntax in a string.
local function fill_params(str, mapping)
	str = str:gsub('{{{([^}]+)}}}',
		function(key)
			return mapping[key] or error("Parameter " .. key .. " not found.")
		end)
	return str
end

local lang = require('Module:languages').getByCode('grc')

local full_link = require('Module:links').full_link
local function entry_link(term, accel)
	return full_link({ lang = lang, term = term, tr = '-', accel = accel }, nil, false)
end

-- Creates a callable table that saves previous transliterations.
-- Helpful because most paradigms have some syncretic forms; particularly useful for neuter forms.
local transliterate = require('Module:fun').memoize(require('Module:grc-translit').tr)

local tag_translit = require('Module:script utilities').tag_translit
local function format_translit(Greek_text)
	return tag_translit(transliterate(Greek_text), lang, 'default')
end

local function get_label_display(dialect)
	return require("Module:labels").get_label_info { label = dialect, lang = lang, nocat = true}.label
end

local function get_stylesheet()
	return require("Module:TemplateStyles")("Module:grc-decl/style.css")
end

local function make_number_table(number_arg)
	local numbers = {}
	for _, number in ipairs{ { 'S', 'Singular' }, { 'D', 'Dual' }, { 'P', 'Plural' } } do
		if number_arg[number[1]] then
			table.insert(numbers, number[2])
		end
	end
	return numbers
end

local function link(alt, accel)
	if alt == '-' then
		return '-'
	end
	
	alt = str_gsub(alt, 'σ$', 'ς') --just in case
	if str_find(alt, '%b()') then
		local no_paren_content, with_paren_content =
				str_gsub(alt, '%b()', ''), str_gsub(alt, '[()]', '')
		
		-- This expands πᾶσῐ(ν) to πᾶσῐ / πᾶσῐν so that both terms can be found
		-- in searches.
		return entry_link(no_paren_content, accel) .. form_separator
			.. entry_link(with_paren_content, accel)
	else
		return entry_link(alt, accel)
	end
end

local function get_header(code, irregular, indeclinable)
	if not code then
		if irregular then
			return 'Irregular declension'
		elseif indeclinable then
			return 'Declension'
		else
			return "??"
		end
	elseif #code > 5 then
		mw.log('grc-decl/header not code')
		return code
	else
		return headers[code] or
			error('No header for the code ' .. code .. '.')
	end
end

-- Case abbreviations used by {{inflection of}}.
local inflection_of_case_abbreviations = {
	N = 'nom', G = 'gen', D = 'dat', A = 'acc', V = 'voc'
}

local function link_form(args, form_code, istitle)
	local form_table = args.adjective and args.atable or args.ctable
	local form = form_table[form_code]
	
	if form_is_empty(form) then return empty_cell end
	
	-- if it is a title form, strip all but the first variation
	if istitle then
		form = str_match(form, '[^,/]+') --capture up to comma or slash (needs standardization)
		form = ustring_gsub(form, '%s+$', '') --strip final whitespace
	else
		-- convert commas to slashes and space the slashes for legibility
		form = ustring_gsub(form, '%s*[/,]%s*', form_separator)
		form_table[form_code] = form
	end
	
	-- concat article
	if (not args.adjective) and args.article[form_code] and form_code:sub(1, 1) ~= 'V' then
		form = args.article[form_code] .. ' ' .. form
	end
	
	local accel_prefix
	if args.adjective then
		-- Generate acceleration information ([[WT:ACCEL]]) for declined forms
		-- and for the comparative and superlative forms.
		if form_code:find('^%u+$') then
			local gender, case, number = form_code:match('^(.)(.)(.)$')
			gender, number = gender:lower(), number:lower()
			case = inflection_of_case_abbreviations[case]
				or error("Case " .. case .. " not recognized.")
			
			-- If feminine nominative singular is absent, masculine and feminine
			-- are probably the same?
			local gender_codes = gender == 'm' and not args.atable.FNS and gender .. '//f'
				or gender
			accel_prefix = gender_codes .. '|' .. case .. '|' .. number
			
			if args.comparative then
				accel_prefix = accel_prefix .. '|comparative'
			elseif args.superlative then
				accel_prefix = accel_prefix .. '|superlative'
			end
		elseif form_code == "comp" then
			accel_prefix = 'comparative'
		elseif form_code == "super" then
			accel_prefix = 'superlative'
		end
	else
		local case, number = form_code:match('^(.)(.)$')
		number = number:lower()
		case = inflection_of_case_abbreviations[case]
			or error("Case '" .. tostring(case) .. "' not found.")
		accel_prefix = case .. '|' .. number
	end
		
	-- Add suffix, and make sure any macrons and breves are included in the
	-- non-lemma entry.
	local accel
	if accel_prefix then
		local origin
		if args.decl_type == "irreg" or args.decl_type == "indecl" then
			origin = args[2]
		else
			for _, number in ipairs { 'S', 'D', 'P' } do
				if args.number[number] then
					local code = 'N' .. number
					if args.adjective then
						code = 'M' .. code
					end
					origin = args[code] or form_table[code] or args[1]
					break
				end
			end
		end
		
		origin = origin or args[1] -- just in case
		
		accel = {form = accel_prefix, lemma = origin}
	end
	
	--[[
		An Ancient Greek word character optionally preceded by a hyphen or
		followed by a sequence of word characters or parentheses.
		Matches -ᾰ́ς, σοῖσι(ν), as well as cases with parentheses in the middle
		of the word.
	]]
	local linked_form = ustring_gsub(form,
		'%-?[' .. word_characters .. '][' .. word_characters .. '()]*',
		function (form)
			return link(form, accel)
		end)
	
	if istitle then
		return linked_form
	else
		return linked_form .. '<br/>' .. format_translit(form)
	end
end

local function make_title(args)
	local title = Array()
	title:insert(get_header(args.declheader, args.irregular, args.indeclinable) .. ' of ')
	
	-- Display the nominative and genitive form for the first number that has
	-- forms.
	for _, number_code in ipairs{ 'S', 'D', 'P' } do
		if args.number[number_code] then
			title:insert(link_form(args, 'N' .. number_code, true) .. '; '
				.. link_form(args, 'G' .. number_code, true))
			break
		end
	end
	
	if args.dial then
		table.insert(args.titleapp, get_label_display(args.dial))
	end
	
	if args.titleapp[1] then
		title:insert(' (' .. table.concat(args.titleapp, ', ') .. ')')
	end
	
	return title:concat()
end

local case_header =
[[
|-
! class="case-header" | {{{case_name}}}
]]
local form_cell =
[[
| class="form" data-accel-col="{{{col}}}" | {{{form}}}
]]
local function make_rows(args, nums)
	local rows = Array()
	
	if not args.adjective then
		case_header = case_header
		form_cell = form_cell
	end
	
	for _, case_name in ipairs(case_names) do
		rows:insert((case_header:gsub("{{{case_name}}}", case_name)))
		local case_abbr = case_name:sub(1, 1)
		for i, number in ipairs(nums) do
			rows:insert(fill_params(form_cell, {
				form = link_form(args, case_abbr .. number:sub(1, 1)),
				col = i,
			}))
		end
	end
	
	return rows:concat()
end

local notes_template =
[[
|-
! class="notes-header" | Notes:
| {{{extra_cell}}}class="notes" colspan="13" | <div class="use-with-mention">{{{notes}}}</div>
]]

local function make_notes(args)
	args.notes = Array(args.notes)
	
	if args.dial ~= 'koi' and args.dial ~= 'gkm' then
		args.notes:insert(1,
			args.dial ~= 'att' and nonAttic_note
			or Attic_note)
	end
	
	if args.user_notes then -- add user notes
		args.notes:insert(args.user_notes)
	end
	
	if args.debug then
		args.notes:insert(args.debug)
	end
	
	if next(args.notes) == nil then
		return ""
	end
	
	return fill_params(notes_template, {
		extra_cell = args.adjective and '\n| ' or '',
		notes = args.notes
			:map(function (note) return "\n* " .. note end):concat(),
	})
end

local number_header =
[[
! class="number-header" | {{{number}}}
]]

local noun_table_top =
[=[
<div class="NavFrame grc-decl">
<div class="NavHead">{{{title}}}</div>
<div class="NavContent">
{| class="inflection-table inflection-table-grc"
! class="case-number-header" | Case / #
]=]

function export.make_table(args)
	local nums = make_number_table(args.number)
	
	if not args.adjective then
		noun_table_top = noun_table_top
		number_header = number_header
	end
	
	-- This can be simplified.
	-- Percents have to be escaped (otherwise, strangely, a null character is
	-- inserted).
	local output = Array(
		(noun_table_top
			:gsub('{{{title}}}', make_title(args))))
	
	for _, number in ipairs(nums) do
		output:insert((number_header:gsub('{{{number}}}', number)))
	end
	output:insert(make_rows(args, nums))
	output:insert(make_notes(args))
	output:insert('|}</div></div>')
	if args.categories[1] and mw.title.getCurrentTitle().nsText == '' then
		output:insert(require('Module:utilities').format_categories(args.categories, lang))
	end
	
	return output:concat() .. get_stylesheet()
end

local function make_title_adj(args, genders)
	if args.title then
		return args.title
	else
		local title = Array()
		title:insert(get_header(args.adeclheader, args.irregular, args.indeclinable) .. ' of ')
		
		for _, number in ipairs{ 'S', 'D', 'P' } do
			if args.number[number] then
				local gender_forms = {}
				for _, gender in ipairs(genders) do
					table.insert(gender_forms, link_form(args, gender .. 'N' .. number, true))
				end
				title:insert(table.concat(gender_forms, '; '))
				break
			end
		end
		
		if args.dial then
			table.insert(args.titleapp, get_label_display(args.dial))
		end
		
		if args.titleapp[1] then
			title:insert(' (' .. table.concat(args.titleapp, ', ') .. ')')
		end
		
		return title:concat()
	end
end

local function make_rows_adj(args, nums, genders)
	local rows = Array()
	
	for _, case_name in ipairs(case_names) do
		rows:insert((case_header:gsub('{{{case_name}}}', case_name)))
		for i, number in ipairs(nums) do
			rows:insert('|\n')
			local case_number = case_name:sub(1, 1) .. number:sub(1, 1)
			for _, gender in ipairs(genders) do
				rows:insert(fill_params(form_cell, {
					form = link_form(args, gender .. case_number),
					col = i
				}))
			end
		end
	end
	return rows:concat()
end

-- Add the part of the table containing the adverb and the comparative and
-- superlative forms, if applicable.
local function make_acs_adj(args, nums)
	-- This should only apply to pronouns. I think.
	-- If all of adverb, comparative, and superlative are absent, don't display
	-- the "derived forms" part of the table at all.
	if #nums < 3 or require 'Module:fun'.all(
			function (form_code)
				return form_is_empty(args.atable[form_code])
			end,
			{ 'adv', 'comp', 'super' }) then
		return ''
	end
	
	args.atable.adv = args.atable.adv
	args.atable.comp = args.atable.comp
	args.atable.super = args.atable.super
	
	local fill = {
		colspan = (args.act and args.act[2] or args.irregular) and '3' or '2',
		adv = link_form(args, 'adv'),
		comp = link_form(args, 'comp'),
		super = link_form(args, 'super'),
	}
	
	local acs_section = [=[
|-
! class="derived-forms-header" rowspan="2" | Derived forms
|
! class="derived-form-name-header" colspan={{{colspan}}} | Adverb
|
! class="derived-form-name-header" colspan={{{colspan}}} | Comparative
|
! class="derived-form-name-header" colspan={{{colspan}}} | Superlative
|-
|
| class="form" colspan={{{colspan}}} | {{{adv}}}
|
| class="form" colspan={{{colspan}}} | {{{comp}}}
|
| class="form" colspan={{{colspan}}} | {{{super}}}
]=]
	
	return fill_params(acs_section, fill)
end

local adj_table_top =
[=[
<div class="NavFrame grc-decl grc-adecl">
<div class="NavHead">{{{title}}}</div>
<div class="NavContent">
{| class="inflection-table inflection-table-grc"
! class="number-header" | Number
]=]

function export.make_table_adj(args)
	local nums = make_number_table(args.number)
	
	local threept = not(args.act and args.act[2] == nil)
	local genders = threept and { 'M', 'F', 'N' } or { 'M', 'N' }
	local number_header =
[=[
! class="divider" |
! class="number-header" colspan=]=] .. (threept and '3' or '2') .. "| {{{number}}}\n"

	local gender_headers =
[=[
|
! class="gender-header" | Masculine]=] .. (threept and [=[
!! class="gender-header" | ]=] or [=[ / ]=]) .. [=[Feminine
!! class="gender-header" | Neuter
]=]

	local output = Array(
		(adj_table_top:gsub('{{{title}}}', make_title_adj(args, genders)))
	)
	for _, number in ipairs(nums) do
		output:insert((number_header:gsub('{{{number}}}', number)))
	end
	output:insert([=[|-
! class="case-gender-header" | Case/Gender
]=])
	for _, _ in ipairs(nums) do
		output:insert(gender_headers)
	end
	output:insert(make_rows_adj(args, nums, genders))
	output:insert(make_acs_adj(args, nums))
	output:insert(make_notes(args))
	output:insert('|}</div></div>')
	
	if args.categories[1] and mw.title.getCurrentTitle().nsText == '' then
		output:insert(require('Module:utilities').format_categories(args.categories, lang))
	end
	
	return output:concat() .. get_stylesheet()
end

return export