Module:User:Benwing2/family tree

--[=[

Authors: User:kc_kennylau, User:JohnC5, User:Erutuon, User:Suzukaze-c

--]=]

local p = {}

local regular_languages = require("Module:languages/alldata") local families = require("Module:families/data")

-- Version of Module:etymology languages/data that chooses the language- -- codiest code of several codes that have the same data. For instance, -- it chooses "de-AT" over "Austrian German". local etymology_languages = require("Module:family tree/etymology languages")

local Array = require("Module:array")

function p.find_subtree(t, code) for _, val in ipairs(t) do		if val.name == code then -- "name" is really code return { val } else local result = p.find_subtree(val, code) if result then return result end end end end

local family_icon = "F" local etymology_language_icon = "E" local proto_language_icon = family_icon local family_with_proto_language_icon = family_icon local function format_node(code, is_protolanguage_or_has_protolanguage) local canonical_name, category_name, class, icon, tooltip if regular_languages[code] then canonical_name = regular_languages[code][1] category_name = canonical_name .. ' language' class = "ft-lang" if is_protolanguage_or_has_protolanguage then class = class .. ' ft-protolang' icon = proto_language_icon end elseif etymology_languages[code] then canonical_name = etymology_languages[code].canonicalName class = "ft-etymlang" icon = etymology_language_icon tooltip = "Etymology language" elseif families[code] then canonical_name = families[code].canonicalName category_name = canonical_name .. " languages" class = "ft-family" if is_protolanguage_or_has_protolanguage then class = class .. ' ft-hasprotolang' icon = family_with_proto_language_icon else icon = family_icon end tooltip = "Language family" end return ''		.. ''		.. canonical_name		.. ' (' .. code .. ') ' .. (icon and ' ' .. icon .. ' ' or '') .. ' ' end

-- If neither options.show_all_families or options.show_etymology_languages is -- falsy, then this function does nothing. local function filter_nested_data(nested_data, options, protolanguage_of, is_protolanguage) if not nested_data then -- ??? return nil else local name = nested_data.name local first_child = nested_data[1] -- This indicates that new_nested_data below should only be returned -- if it contains non-etymology languages. local check_for_non_etymology_children = false -- If `show_all_families` is false and this is a family and its only -- child is its proto-language, then replace the family with the -- proto-language. if options.hide_families_with_protolanguages and name and families[name] and first_child and not nested_data[2] and protolanguage_of[name] == first_child.name then is_protolanguage[first_child.name] = true return filter_nested_data(first_child, options, protolanguage_of, is_protolanguage) elseif options.hide_etymology_languages and etymology_languages[name] then if nested_data[1] then check_for_non_etymology_children = true else return nil end end local new_nested_data = { name = name } local i = 0 for _, subtable in ipairs(nested_data) do			subtable = filter_nested_data(subtable, options, protolanguage_of, is_protolanguage) if subtable then i = i + 1 new_nested_data[i] = subtable end end if not check_for_non_etymology_children or new_nested_data[1] then return new_nested_data end end end

local function make_node(code, is_protolanguage, protolanguage_of) return ' ' .. format_node(code,		is_protolanguage[code] or protolanguage_of[code] ~= nil) end

local function only_child_is_protolanguage(tree, options, protolanguage_of) return (options.family_under_protolanguage		or options.protolanguage_under_family) and tree[1] and protolanguage_of[tree.name] == tree[1].name end

p.are_all_children_etymology_languages = require "Module:fun".memoize(function (nested_data)	if not nested_data[1] then		return nil	end	for _, child in ipairs(nested_data) do		if not etymology_languages[child.name]		or p.are_all_children_etymology_languages(child) == false then			return false		end	end	return true end)

local customcollapsible_id = 0 local no_break_space = mw.ustring.char(0xA0) local level_separator = (no_break_space):rep(3) local expandtext, collapsetext = "[+]─", "[-]┬" local function make_tree(data, is_protolanguage, protolanguage_of, options, prefix) local result = Array -- This tag is closed in the node generated by make_node. prefix = prefix or ' ' local branch = "├" local next_level = prefix .. "│" .. level_separator local length = #data for i, val in ipairs(data) do		if i == length then branch = "└" next_level = prefix .. level_separator .. no_break_space end local code = val.name local language_or_family_node = make_node(code, is_protolanguage, protolanguage_of) if not val[1] then result:insert('' .. prefix .. branch .. options.sterile_branch_text				.. language_or_family_node .. '') else customcollapsible_id = customcollapsible_id + 1 result:insert('' .. prefix .. branch				.. '───┬ ') -- name me! local flag = (options.family_under_protolanguage				or options.protolanguage_under_family) and only_child_is_protolanguage(val, options, protolanguage_of) local top_node if flag then code = val[1].name val = val[1] top_node = make_node(code, is_protolanguage, protolanguage_of) if options.protolanguage_under_family then top_node, language_or_family_node = language_or_family_node, top_node end end local all_children_are_etymology_languages = p.are_all_children_etymology_languages(val) local collapsible_ul = '' if flag then result:insert(top_node					.. collapsible_ul .. '' .. prefix					.. (i == length and no_break_space or "│")					.. level_separator .. "│") end result:insert(language_or_family_node) if not flag then result:insert(collapsible_ul) end -- Can't get default collapsibility script to apply the data-expandtext -- and data-collapsetext attribute values to the custom toggle, -- so have to have a custom script do it. result:insert(make_tree(val, is_protolanguage, protolanguage_of, options, next_level)) result:insert('') end end return result:concat end

local function get_number_parameter_in_range(args, arg, low, high) local val = args[arg] if val == "" or val == nil then val = nil else val = tonumber(val) if not (type(val) == "number"		and 0 <= val and val <= 6) then error("Expected nothing or number between " .. low .. " and "				.. high .. " in parameter |" .. arg .. "=.") end end return val end

function p.show(frame) local args = frame.args local descendants_of = args[1] if descendants_of == "" then descendants_of = nil elseif not (regular_languages[descendants_of] or families[descendants_of]) then error("The language code " .. descendants_of			.. " is not a valid non-etymology language or family.") end local to_boolean = require("Module:yesno") -- Determines whether families that have proto-languages will be shown. local show_all_families = to_boolean(args[2] or args.fam) -- Determines whether all etymology languages will be shown. local show_etymology_languages = to_boolean(args[3] or args.etym) -- help! parameter name too long! local sterile_branch_length = get_number_parameter_in_range(args, "sterile_branch_length", 0, 6) -- Determines whether (if all families are shown) a family will be shown -- on a line directly under and at the same level as its proto-language, -- or the proto-language on a line directly under and at the same level as -- its family. local family_under_protolanguage = to_boolean(args.famunderproto) local protolanguage_under_family = to_boolean(args.protounderfam) if family_under_protolanguage and protolanguage_under_family then error("Kindly choose between proto-language under family and family under proto-language.") end return p.print_children(descendants_of, {		hide_families_with_protolanguages = not show_all_families,		hide_etymology_languages = not show_etymology_languages,		family_under_protolanguage = family_under_protolanguage,		protolanguage_under_family = protolanguage_under_family,		sterile_branch_length = sterile_branch_length,		collapsed = require("Module:yesno")(args.collapsed)	}) end

function p.print_children(descendants_of, options) local data = require("Module:family tree/nested data") local nested_data, protolanguage_of = data.nested, data.protolanguage_of if descendants_of then nested_data = p.find_subtree(nested_data, descendants_of) end -- Return nil instead of a tree with only the root node. if options.must_have_descendants and (#nested_data == 0 or nested_data[1] and #nested_data[1] == 0) then return nil end local is_protolanguage = {} if options.hide_families_with_protolanguages or options.hide_etymology_languages then nested_data = filter_nested_data(nested_data, {			hide_families_with_protolanguages = options.hide_families_with_protolanguages,			hide_etymology_languages = options.hide_etymology_languages,		}, protolanguage_of, is_protolanguage) end if not nested_data or not next(nested_data) then return nil end local result = Array(' ') local tree_options = { sterile_branch_text = ' ' .. ("─"):rep(options.sterile_branch_length or 4) .. ' ',		family_under_protolanguage = options.family_under_protolanguage, protolanguage_under_family = options.protolanguage_under_family, }	local collapsetext, expandtext = 'Collapse', 'Expand' for i, subtable in ipairs(nested_data) do		-- top language name result:insert('') -- name me! local flag = (options.family_under_protolanguage			or options.protolanguage_under_family) and only_child_is_protolanguage(subtable, options, protolanguage_of) local top_node = format_node(subtable.name) local next_node if flag then subtable = subtable[1] next_node = format_node(subtable.name) if options.family_under_protolanguage then top_node, next_node = next_node, top_node end end result:insert(top_node) -- top toggle customcollapsible_id = customcollapsible_id + 1 result:insert('') result:insert(options.collapsed and expandtext or collapsetext) result:insert(' ') if flag then result:insert('') result:insert(next_node) end -- tree result:insert('') result:insert(make_tree(subtable, is_protolanguage, protolanguage_of, tree_options, nil)) if flag then result:insert('') end result:insert('</li>') end result:insert('</ul> ') result:insert(require("Module:TemplateStyles")("Module:User:Benwing2/family tree/style.css")) return result:concat end

function p.print_data(frame) return require("Module:debug").highlight_dump(require("Module:family tree/nested data")) end

-- Mainly for testing. function p.list_descendants_of_frame(frame) local parent = frame.args[1] if not parent then error("Provide parent code in parameter 1.") end return table.concat(p.list_descendants_of(parent), "\n") end

function p.list_descendants_of(parent) local subtree = p.find_subtree(require("Module:family tree/nested data").nested, parent) local function gather_descendant_codes(subtree, descendants) for _, lang in ipairs(subtree) do			table.insert(descendants, lang.name) gather_descendant_codes(lang, descendants) end end local codes = {} gather_descendant_codes(subtree[1], codes) table.sort(codes) return codes end

return p