Module:Cs1 documentation support

require('Module:No globals'); local getArgs = require ('Module:Arguments').getArgs;

local cfg = mw.loadData ('Module:Citation/CS1/Configuration');					-- load the configuration module

local exclusion_lists = {														-- TODO: move these tables into a separate ~/data module and mw.loadData it	['cite book'] = { ['agency'] = true, ['air-date'] = true, ['arxiv'] = true, ['biorxiv'] = true, ['citeseerx'] = true, ['class'] = true, ['conference'] = true, ['conference-format'] = true, ['conference-url'] = true, ['degree'] = true, ['department'] = true, ['display-interviewers'] = true, ['docket'] = true, ['episode'] = true, ['interviewer#'] = true, ['interviewer-first#'] = true, ['interviewer-link#'] = true, ['interviewer-mask#'] = true, ['ismn'] = true, ['issn'] = true, ['issue'] = true, ['jfm'] = true, ['journal'] = true, ['jstor'] = true, ['mailinglist'] = true, ['message-id'] = true, ['minutes'] = true, ['MR'] = true, ['network'] = true, ['number'] = true, ['RFC'] = true, ['script-journal'] = true, ['season'] = true, ['section'] = true, ['sections'] = true, ['series-link'] = true, ['series-number'] = true, ['series-separator'] = true, ['sheet'] = true, ['sheets'] = true, ['SSRN'] = true, ['station'] = true, ['time'] = true, ['time-caption'] = true, ['trans-article'] = true, ['trans-journal'] = true, ['transcript'] = true, ['transcript-format'] = true, ['transcript-url'] = true, ['ZBL'] = true, },	['cite journal'] = { ['agency'] = true, ['air-date'] = true, ['book-title'] = true, ['chapter'] = true, ['chapter-format'] = true, ['chapter-url'] = true, ['chapter-url-access'] = true, ['class'] = true, ['conference'] = true, ['conference-format'] = true, ['conference-url'] = true, ['contribution'] = true, ['contributor#'] = true, ['contributor-first#'] = true, ['contributor-link#'] = true, ['contributor-mask#'] = true, ['degree'] = true, ['department'] = true, ['display-interviewers'] = true, ['docket'] = true, ['edition'] = true, ['editor#'] = true, ['editor-first#'] = true, ['editor-link#'] = true, ['editor-mask#'] = true, ['editors'] = true, ['encyclopedia'] = true, ['episode'] = true, ['ignore-isbn-error'] = true, ['interviewer#'] = true, ['interviewer-first#'] = true, ['interviewer-link#'] = true, ['interviewer-mask#'] = true, ['isbn'] = true, ['ismn'] = true, ['LCCN'] = true, ['mailinglist'] = true, ['message-id'] = true, ['minutes'] = true, ['network'] = true, ['script-chapter'] = true, ['season'] = true, ['section'] = true, ['sections'] = true, ['series-link'] = true, ['series-number'] = true, ['series-separator'] = true, ['sheet'] = true, ['sheets'] = true, ['station'] = true, ['time'] = true, ['time-caption'] = true, ['trans-article'] = true, ['transcript'] = true, ['transcript-format'] = true, ['transcript-url'] = true, },	}

--[[-< A D D _ T O _ L I S T >-

adds code/name pair to code_list and name/code pair to name_list; code/name pairs in override_list replace those taken from the MediaWiki list; these are marked with a superscripted dagger.


 * script- = lang codes always use override names so dagger is omitted

]]

local function add_to_list (code_list, name_list, override_list, code, name, dagger) if false == dagger then dagger = '';															-- no dagger for |script- = codes and names else dagger = '†';												-- dagger for all other lists using override end

if override_list[code] then													-- look in the override table for this code code_list[code] = override_list[code] .. dagger;						-- use the name from the override table; mark with dagger name_list[override_list[code]] = code .. dagger; else code_list[code] = name;													-- use the MediaWiki name and code name_list[name] = code; end end

--[[-< L I S T _ F O R M A T >-

formats key/value pair into a string for rendering ['k'] = 'v'	→ k: v

]]

local function list_format (result, list) for k, v in pairs (list)	do table.insert (result, k .. ': ' .. v); end end

--[[-< L A N G _ L I S T E R >-

Module entry point

Crude documentation tool that returns one of several lists of language codes and names.

Used in Template:Citation Style documentation/language/doc

{{#invoke:cs1 documentation support|lang_lister|list= |lang= ', name);		-- same as {{para| }}	end

return string.format ('%s', args.id or '', name);			-- because {{csdoc}} bolds param names end

--[[--< A L I A S _ N A M E S _ G E T >

returns list of aliases for metaparameter returns empty string when there are no aliases returns empty string when name not found in alias_src{} or id_src{}; access icon parameters have no aliases so ignored

metaparameter is the key in Module:Citation/CS1 aliases{} table or id_handlers{} table.

Some lists of aliases might be better served when a particular alias is identified as the canonical alias for a particular use case. If, for example, Perodical lists: 'journal', 'magazine', 'newspaper', 'periodical', 'website', 'work' that order works fine for {{cite journal}} documentation but doesn't work so well for {{cite magazine}}, {{cite news}}, or {{cite web}}. So, for using this function to document {{cite magazine}} the returned value should be the aliases that are not best suited for that template so we can specify magazine in the override (frame.args[2]) to be the canonical parameter so it won't be listed with the rest of the aliases (normal canonical journal will be)

must exist in the alias list except: when value is 'all', returns the canonical parameter plus all of the aliases

output format is controlled by |format= plain - renders in plain text in a tag; may have id attribute para - renders as it would in {{para| }} when not specified, refurns the default bold format used for {{csdoc}}

{{#invoke:cs1 documentation support|alias_name_get| | |format=[plain|para]}}

]]

local function alias_names_get (frame) local alias_src = cfg.aliases;												-- get master list of aliases local id_src = cfg.id_handlers;												-- get master list of identifiers local args = getArgs (frame); local meta = args[1]; local override = args[2];

local out = {}; local source;																-- selected parameter or id aliases list local aliases;

source = alias_src[meta] or (id_src[meta] and id_src[meta].parameters); if not source then if meta:match ('%u+access') then return 'no' == args.none and '' or 'none';							-- custom access parameters don't have aliases else return '';															-- no such meta end elseif not source[2] then													-- id_source[meta] is always a table; if no second member, no aliases return 'no' == args.none and '' or 'none'; end if not override then aliases = source;														-- normal skip-canonical param case else local flag = 'all' == override and true or nil;							-- so that we know that parameter is a valid alias; spoof when override == 'all' aliases = {[1] = ''};													-- spoof to push alias_src[meta][1] and id_src[meta][1] into aliases[2] for _, v in ipairs (source) do											-- here when override is set; spin through the aliases to make sure override matches alias in table if v ~= override then table.insert (aliases, v);										-- add all but overridden param to the the aliases list for this use case else flag = true;													-- set the flag so we know that is a valid alias end end if not flag then aliases = {}														-- unset the table as error indicator end end

if 'table' == type (aliases) then											-- table only when there are aliases for i, alias in ipairs (aliases) do			if 1 ~= i then														-- aliases[1] is the canonical name; don't include it				if 'plain' == args.format then									-- format and return the output table.insert (out, alias);									-- plain text elseif 'para' == args.format then table.insert (out, string.format (' ', alias));	-- same as {{para| }} else table.insert (out, string.format ("%s", alias));		-- because csdoc bolds param names end end end return table.concat (out, ', ');										-- make pretty list and quit end

return 'no' == args.none and '' or 'none';									-- no metaparameter with that name or no aliases end

--[[--< I S _ B O O K _ C I T E _ T E M P L A T E >

fetch the title of the current page; if it is a preprint template, return true; empty string else

]]

local book_cite_templates = { ['citation'] = true, ['cite book'] = true, }

local function is_book_cite_template local title = mw.title.getCurrentTitle.rootText;							-- get title of current page without namespace and without sub-pages; from Template:Cite book/new -> Cite book title = title and title:lower or ''; return book_cite_templates[title] or ''; end

--[[--< I S _ L I M I T E D _ P A R A M _ T E M P L A T E >

fetch the title of the current page; if it is a preprint template, return true; empty string else

]]

local limited_param_templates = {												-- if ever there is a need to fetch info from ~/Whitelist then ['cite arxiv'] = true,														-- this list could also be fetched from there ['cite biorxiv'] = true, ['citeseerx'] = true, ['ssrn'] = true, }

local function is_limited_param_template local title = mw.title.getCurrentTitle.rootText;							-- get title of current page without namespace and without sub-pages; from Template:Cite book/new -> Cite book title = title and title:lower or ''; return limited_param_templates[title] or ''; end

--[[--< H E A D E R _ M A K E >

makes a section header from  and ; defaults to 2; cannot be less than 2

]]

local function _header_make (args) if not args[1] then return '';																-- no header text end local level = args[2] and tonumber (args[2]) or 2; level = string.rep ('=', level); return level .. args[1] .. level; end

--[[--< H E A D E R _ M A K E >

Entry from an {{#invoke:}} makes a section header from  and ; defaults to 2; cannot be less than 2

]]

local function header_make (frame) local args = getArgs (frame); return _header_make (args); end

--[[--< I D _ L I M I T S _ G E T >

return the limit values for named identifier parameters that have  limits (pmc, pmid, ssrn, s2cid, oclc, osti, rfc); the return value used in template documentation and error message help-text

{{#invoke:Cs1 documentation support|id_limits_get|}}

]]

local function id_limits_get (frame) local args = getArgs (frame); local handlers = cfg.id_handlers;											-- get id_handlers {} table from ~/Configuration

return args[1] and handlers[args[1]:upper].id_limit or (' No limit defined for identifier: ' .. (args[1] or ' ') .. ' '); end

----< C A T _ L I N K _ M A K E >

local function cat_link_make (cat) return table.concat ({'Category:', cat, ''}); end

--[[--< S C R I P T _ C A T _ L I S T E R >

utility function to get script-language categories

]]

local lang_list_t = mw.language.fetchLanguageNames ('en', 'all'); local function script_cat_lister (script_lang_codes_t, lang_code_remap_t, cats_list_t) for _, lang_code in ipairs (script_lang_codes_t) do		local lang_name = lang_code_remap_t[lang_code] or lang_list_t[lang_code];	-- use remap table to get Bengali instead of Bangla and the like; else use standard MediaWiki names local cat = 'CS1 uses ' .. lang_name .. '-language script (' .. lang_code .. ')';	-- build a category name cats_list_t[cat] = 1;													-- and save it	end end

--[[--< C S 1 _ C A T _ L I S T E R >--

This is a crude tool that reads the category names from Module:Citation/CS1/Configuration, makes links of them, and then lists them in sorted lists. A couple of parameters control the rendering of the output: |select=	-- (required) takes one of three values: error, maint, prop |sandbox=	-- takes one value: no	|hdr-lvl=	-- base header level (number of == that make a header); default:2 min:2

This tool will automatically attempt to load a sandbox version of ~/Configuration if one exists. Setting |sandbox=no will defeat this.

{{#invoke:cs1 documentation support|cat_lister|select=|sandbox=}}

]]

local function cat_lister (frame) local args = getArgs (frame);

local list_live_cats = {};													-- list of live categories local list_sbox_cats = {};													-- list of sandbox categories local live_sbox_out = {}													-- list of categories that are common to live and sandbox modules local live_not_in_sbox_out = {}												-- list of categories in live but not sandbox local sbox_not_in_live_out = {}												-- list of categories in sandbox but not live local out = {};																-- final output assembled here local sandbox;																-- boolean; true: evaluate the sandbox module local hdr_lvl;																-- local sb_cfg; local sandbox, sb_cfg = pcall (mw.loadData, 'Module:Citation/CS1/Configuration/sandbox');	-- get sandbox configuration

local cat;

local select = args.select; if 'no' == args.sandbox then												-- list sandbox? sandbox = false;														-- no, live only end if hdr_lvl then																-- if set and if tonumber (hdr_lvl) then												-- can be converted to number if 2 > tonumber (hdr_lvl) then										-- min is 2 hdr_lvl = 2;													-- so set to min end else																	-- can't be converted hdr_lvl = 2;														-- so default to min end else hdr_lvl = 2;															-- not set so default to min end

if 'error' == select or 'maint' == select then								-- error and main categorys handling different from poperties cats for _, t in pairs (cfg.error_conditions) do								-- get the live module's categories if ('error' == select and t.message) or ('maint' == select and not t.message) then cat = t.category:gsub ('|(.*)$', '');							-- strip sort key if any list_live_cats[cat] = 1;										-- add to the list end end if sandbox then															-- if ~/sandbox module exists and |sandbox= not set to 'no' for _, t in pairs (sb_cfg.error_conditions) do						-- get the sandbox module's categories if ('error' == select and t.message) or ('maint' == select and not t.message) then cat = t.category:gsub ('|(.*)$', '');						-- strip sort key if any list_sbox_cats[cat] = 1;									-- add to the list end end end elseif 'prop' == select then												-- prop cats for _, cat in pairs (cfg.prop_cats) do									-- get the live module's categories cat = cat:gsub ('|(.*)$', '');										-- strip sort key if any list_live_cats[cat] = 1;											-- add to the list end

script_cat_lister (cfg.script_lang_codes, cfg.lang_code_remap, list_live_cats);	-- get live module's foriegn language script cats

if sandbox then															-- if ~/sandbox module exists and |sandbox= not set to 'no' for _, cat in pairs (sb_cfg.prop_cats) do							-- get the sandbox module's categories cat = cat:gsub ('|(.*)$', '');									-- strip sort key if any list_sbox_cats[cat] = 1;										-- add to the list end

script_cat_lister (sb_cfg.script_lang_codes, sb_cfg.lang_code_remap, list_sbox_cats);	-- get sandbox module's foriegn language script cats end else return 'error: unknown selector: ' .. select .. ' '	end

for k, _ in pairs (list_live_cats) do										-- separate live/sbox common cats from cats not in sbox if not list_sbox_cats[k] and sandbox then table.insert (live_not_in_sbox_out, cat_link_make (k));				-- in live but not in sbox else table.insert (live_sbox_out, cat_link_make (k));					-- in both live and sbox end end

for k, _ in pairs (list_sbox_cats) do										-- separate sbox/live common cats from cats not in live if not list_live_cats[k] then table.insert (sbox_not_in_live_out, cat_link_make (k));				-- in sbox but not in live end end

local function comp (a, b)													-- local function for case-agnostic category name sorting return a:lower < b:lower; end

local header;																-- initialize section header with name of selected category list if 'error' == select then header = 'error'; elseif 'maint' == select then header = 'maintenance'; else header = 'properties'; end header = table.concat ({													-- build the main header		'Live ',																-- always include this		((sandbox and 'and sandbox ') or ''),									-- if sandbox evaluated, mention that		header,																	-- add the list name		' categories (',														-- finish the name and add #live_sbox_out,															-- count of categories listed ')'																		-- close	})

local templatestyles = frame:extensionTag{ name = 'templatestyles', args = { src = "Div col/styles.css" } }

header = table.concat ({													-- make a useable header		_header_make ({header, hdr_lvl}),		'\n' .. templatestyles .. ' '	-- opening for columns		});

table.sort (live_sbox_out, comp);											-- sort case agnostic acsending table.insert (live_sbox_out, 1, header);									-- insert the header at the top table.insert (out, table.concat (live_sbox_out, '\n*'));					-- make a big string of unordered list markup table.insert (out, ' \n');												-- close the and add new line so the next header works

if 0 ~= #live_not_in_sbox_out then											-- when there is something in the table header = table.concat ({												-- build header for subsection			'In live but not in sandbox (', #live_not_in_sbox_out, ')'			});		header = table.concat ({												-- make a useable header			_header_make ({header, hdr_lvl+1}),			'\n' .. templatestyles .. ' '			}); table.sort (live_not_in_sbox_out, comp); table.insert (live_not_in_sbox_out, 1, header); table.insert (out, table.concat (live_not_in_sbox_out, '\n*')); table.insert (out, ' \n'); end if 0 ~= #sbox_not_in_live_out then											-- when there is something in the table header = table.concat ({												-- build header for subsection			'In sandbox but not in live (', #sbox_not_in_live_out, ')'			});		header = table.concat ({												-- make a useable header			_header_make ({header, hdr_lvl+1}),			'\n' .. templatestyles .. ' '			}); table.sort (sbox_not_in_live_out, comp); table.insert (sbox_not_in_live_out, 1, header); table.insert (out, table.concat (sbox_not_in_live_out, '\n*')); table.insert (out, ' \n'); end

return table.concat (out);													-- concat into a huge string and done end

--[=[--< H E L P _ T E X T _ C A T S >--

To create category links at the bottom of each error help text section and on the individual error category pages; fetches category names from ~/Configuration; replaces this: {{#ifeq:{{FULLPAGENAME}}|Category:CS1 errors: bioRxiv|Category:CS1 errors: bioRxiv|Category:CS1 errors: bioRxiv}} with this: {{#invoke:Cs1 documentation support|help_text_cats|err_bad_biorxiv}}

add |pages=yes to append the number of pages in the category ]=]

local function help_text_cats (frame) local args = getArgs (frame); local error_conditions = mw.loadData ('Module:Citation/CS1/Configuration').error_conditions; local out = {};																-- output goes here if args[1] and error_conditions[args[1]] then								-- must have error_condition key and it must exist table.insert (out, '{{#ifeq:{{FULLPAGENAME}}|Category:');				-- the beginning with category prefix table.insert (out, error_conditions[args[1]].category);					-- fetch the error category name (the reference that {{FULLPAGENAME}} must equate to) table.insert (out, '|Category:');										-- this category plain text when this invoke is on the category page table.insert (out, error_conditions[args[1]].category);					-- fetch the error category name table.insert (out, '|');												-- the necessary pipe table.insert (out, cat_link_make (error_conditions[args[1]].category));	-- one the help page or elsewhere so link to the category table.insert (out, '}}');												-- and close the #ifeq if 'yes' == args.pages then table.insert (out, ' ({{PAGESINCATEGORY:');			table.insert (out, error_conditions[args[1]].category);				-- fetch the error category name			table.insert (out, '}} pages)'); end else return ' unknown error_conditions key: ' .. (args[1] or 'key missing') .. ' ';	end return frame:preprocess (table.concat (out));								-- make a big string, preprocess, and done end

--[[--< H E L P _ T E X T _ E R R O R _ M E S S A G E >

to render help text example error messages {{#invoke:Cs1 documentation support|help_text_error_messages|err_bad_biorxiv}}

assign a single underscore to any of the |$n= parameters to insert an empty string in the error message: {{#invoke:Cs1 documentation support|help_text_error_messages|err_bad_issn|$1=_}} -> Check |issn= value {{#invoke:Cs1 documentation support|help_text_error_messages|err_bad_issn|$1=e}} -> Check |eissn= value ]]

local function help_text_error_messages (frame) local args = getArgs (frame); local error_conditions = mw.loadData ('Module:Citation/CS1/Configuration').error_conditions; local span_o = ' '; local span_c = ' ';

local message; local out = {};																-- output goes here if args[1] and error_conditions[args[1]] then								-- must have error_condition key and it must exist message = error_conditions[args[1]].message; local i=1; local count; local rep; repeat rep = '$'..i			args[rep] = args[rep] and args[rep]:gsub ('^%s*_%s*$', '') or nil;	-- replace empty string marker with actual empty string message, count = message:gsub (rep, args[rep] or rep) i = i + 1; until (0 == count);

table.insert (out, span_o); table.insert (out, message); table.insert (out, span_c); else return ' unknown error_conditions key: ' .. (args[1] or 'key missing') .. ' ';	end local out_str = table.concat (out); out_str = frame:expandTemplate ({title='resize', args = {'120%', out_str} }) return table.concat ({out_str, frame:extensionTag ('templatestyles', '', {src='Module:Citation/CS1/styles.css'})}); end

----< E X P O R T E D  F U N C T I O N S >--

return { alias_lister = alias_lister, alias_names_get = alias_names_get, canonical_param_lister = canonical_param_lister, canonical_name_get = canonical_name_get, cat_lister = cat_lister, header_make = header_make, help_text_cats = help_text_cats, help_text_error_messages = help_text_error_messages, id_limits_get = id_limits_get, is_book_cite_template = is_book_cite_template, is_limited_param_template = is_limited_param_template, lang_lister = lang_lister, script_lang_lister = script_lang_lister, };