Module:WPSHIPS utilities/sandbox explained

require('strict')local get_args = require ('Module:Arguments').getArgs;local styles = require ('Module:WPMILHIST Infobox style'); -- infobox csslocal data = mw.loadData ('Module:WPSHIPS utilities/data/sandbox');local namespace = mw.title.getCurrentTitle.namespace; -- used for categorization

local error_map =

----------------------------< M A K E _ E R R O R _ M S G >--------------------------------------------------

assembles an error message from message text, help link, and error category.

local function make_error_msg (msg, cat, no_cat) local out = ; local category; table.insert (out, '

Error: '); table.insert (out, error_map[msg][1]); table.insert (out, table.concat); table.insert (out, ''); if (0

namespace or 10

namespace) and not no_cat then -- categorize in article space (and template space to take care of broken usages) table.insert (out, table.concat); end

return table.concat (out);end

----------------------------< I S _ S E T >------------------------------------------------------------------

Returns true if argument is set; false otherwise. Argument is 'set' when it exists (not nil) or when it is not an empty string.

local function is_set(var) return not (var

nil or var

);end

--

local function sizeof_ship_type (frag, frag_len, nat_len) local ship_type; if 5 <= (frag_len - nat_len) then -- must have at least five fragments after nationality for four-word ship type ship_type = table.concat (frag, ' ', nat_len+1, nat_len+4); -- four-word ship type if data.ship_type_t[ship_type] then return 4; end end if 4 <= (frag_len - nat_len) then -- must have at least four fragments after nationality for three-word ship type ship_type = table.concat (frag, ' ', nat_len+1, nat_len+3); -- three-word ship type if data.ship_type_t[ship_type] then return 3; end end if 3 <= (frag_len - nat_len) then -- must have at least three fragments after nationality for two-word ship type ship_type = table.concat (frag, ' ', nat_len+1, nat_len+2); -- two-word ship type if data.ship_type_t[ship_type] then return 2; end end if 2 <= (frag_len - nat_len) then -- must have at least two fragments after nationality for one-word ship type if data.ship_type_t[frag[nat_len+1]] then -- one-word ship type return 1; end end return 0; -- no recognizable ship typeend

--

local function sizeof_nationality (frag, frag_len) local nat = ;

if not data.nationality_t [frag[1]] then -- if not a one-word nationality if 2 <= frag_len - 2 then -- must have at least two fragments after nationality for minimal ship type and name nat = table.concat (frag, ' ', 1, 2); if data.nationality_t [nat] then -- is it a two-word nationality? return 2; -- yes else return 0; -- no end end return 0; -- not one-word and not enough fragments for two-word end return 1; -- one-word nationalityend

--name= (required): a name is required; if missing or empty, this function returns an error message (may or may not be visible depending on where it is used) used in

to provide a value for and to provide a value for |infobox caption= Optional arguments to support

: |dab=none – displays ship name without parenthetical disambiguator; use when |infobox caption=nodab |sclass=2 – for ship classes only; displays class name without italics (parameter name is loosely similar to which does the same thing); use when |infobox caption=class |adj=off – for ship classes only; displays class name as a noun (no hyphen, no ship type); use when |infobox caption=class

Arguments are passed in a table.

to call this function locally: do_ship_name_format or args = ; do_ship_name_format (args)

The function returns the formatted name or, if unable to format the name, the original name and an unformatted error message.

local function do_ship_name_format (args) local name_sans_dab; -- the ship or class name without a trailing parenthetical dab local dab; -- the dab stripped from the name local fragments = ; -- a table of words that make up name_sans_dab local ship_type; -- a word or phrase that describes a ship local type_len; -- the number of words that describe a ship local nat_len; -- the number of words used to specify a ship's nationality local name = ; -- the reassembles and formatted ship name local error_msg = ; -- a repository for error messages if any

-- args.name = mw.text.decode (args.name); -- replace html entities in title with their characters; doesn't work for & and & in prefix args.name = args.name:gsub ("'", "\'"); -- replace html appostrophe with the character-- args.name = args.name:gsub ("&", "&");-- args.name = args.name:gsub ("&", "&");-- args.name = args.name:gsub ("&", "&");

if args.name:match ('.+%-class%s+%a+') then -- if a ship-class fragments = mw.text.split (args.name, '-class'); -- split at -class if '2'

args.sclass then -- for DISPLAYTITLE and infobox caption when class not named for a member of the class if 'off'

args.adj then return fragments[1] .. ' class'; -- for infobox caption do noun form class (no hyphen, no ship type) end return args.name; -- nothing to do so return original unformatted name end if 'off'

args.adj then return "" .. fragments[1] .. " class"; -- for infobox caption do noun form class (no hyphen, no ship type) end return "" .. fragments[1] .. "-class" .. fragments[2]; -- and return formatted adjectival name end -- not a ship class so try to format a ship name name_sans_dab, dab = args.name:match('^(.+)%s+(%b)%s*$'); -- split name into name_sans_dab and dab if is_set (dab) then dab = ' ' .. dab; -- insert a space for later reassembly else name_sans_dab = args.name; -- because without a dab, the string.match returns nil dab = ; -- empty string for concatenation end fragments = mw.text.split (name_sans_dab, '%s'); -- split into a table of separate words

nat_len = sizeof_nationality (fragments, #fragments); -- get the number of words in the ship's nationality if 0 < nat_len then -- if not zero we have a valid nationality type_len = sizeof_ship_type (fragments, #fragments, nat_len); -- get the number of words in the ship type if 0 < type_len then -- if not zero, ship type is valid; nationality and type not italics, the rest is name name = "" .. table.concat (fragments, ' ', nat_len + type_len + 1) .. ""; -- format name if 'none'

args.dab then -- for |infobox caption=nodab return name; -- return the formatted name without the nationality or ship type or dab end name = table.concat (fragments, ' ', 1, nat_len + type_len) .. " " .. name; -- assemble everything but dab else error_msg = ' unrecognized ship type;'; -- valid nationality, invalid ship type end elseif data.ship_prefix_t[fragments[1]] then -- if the first fragment is a ship prefix name = table.remove (fragments, 1); -- fetch it from the table name = name .. " " .. table.concat (fragments, ' ') .. ""; -- assemble formatted name else error_msg = ' no nationality or prefix;'; -- invalid nationality and first word in ship name not a valid prefix end

if is_set (name) then -- name will be set if we were able to format it if 'none'

args.dab then -- for |infobox caption=nodab return name; -- return the formatted name without the dab end return name .. dab; -- return the formatted name with the dab end if is_set (dab) then if dab:match ('%(%u+[%- ]?%d+%)') or -- one or more uppercase letters, optional space or hyphen, one or more digits dab:match ('%(%d+[%- ]?%u+%)') or -- one or more digits, optional space or hyphen, one or more uppercase letters dab:match ('%(%u[%u%-]*%-%d+%)') or -- one or more uppercase letters with hyphens, a hyphen, one or more digits (e.g., T-AO-157) dab:match ('%([12]%d%d%d%)') then -- four digits representing year in the range 1000–2999 name = "" .. table.concat (fragments, ' ') .. ""; -- format the name if 'none'

args.dab then -- for |infobox caption=nodab return name; -- return the formatted name without the dab end return name .. dab; -- return the formatted name with the dab end -- last chance, is there a ship type in the dab? for key, _ in pairs (data.ship_type_t) do -- spin through the ship type list and see if there is a ship type (key) in the dab if dab:find ('%f[%a]' .. key .. '%f[^%a]') then -- avoid matches that are not whole word name = "" .. table.concat (fragments, ' ') .. ""; -- format the name if 'none'

args.dab then -- for |infobox caption=nodab return name; -- return the formatted name without the dab end return name .. dab; -- return the formatted name with the dab end end error_msg = error_msg .. ' no ship type in dab;'; if 'none'

args.dab then -- for |infobox caption=nodab return table.concat (fragments, ' '), error_msg; -- return the unformatted name without the dab, and an error message end end

return args.name, error_msg; -- return original un-formatted name with unformatted error message if anyend

--name= (required): a name is required; if missing or empty, this function returns an error message (may or may not be visible depending on where it is used) used in

to provide a value for and to provide a value for |infobox caption= Optional parameters to support

: |dab=none – displays ship name without parenthetical disambiguator; use when |infobox caption=nodab |sclass=2 – for ship classes only; displays class name without italics (parameter name is loosely similar to which does the same thing); use when |infobox caption=class |adj=off – for ship classes only; displays class name as a noun (no hyphen, no ship type); use when |infobox caption=classOther optional parameters: |showerrs=yes – marginally useful; can display error messages if the module invocation is not buried in a template Values from the above parameters are placed in a table and that table passed as an argument in the call to do_ship_name_format.

do_ship_name_format returns two strings: a name and an error message. If do_ship_name_format could format the name, it returns the formatted name and an empty string for the error message. If it could not format the name, do_ship_name_format returns the original name and an error message.

Formatting of the error message, in response to |showerrs=yes is the responsibility of the calling function.

local function ship_name_format(frame) local name = ; -- destination of the formatted ship name local error_msg = ; -- destination of any error message

if not is_set (frame.args.name) then -- if a ship name not provided if 'yes'

frame.args.showerrs then -- and we're supposed to show errors error_msg = '

Empty name'; -- return an empty string error message if there is no name end else name, error_msg = do_ship_name_format (frame.args); -- get formatted name and error message if is_set (error_msg) and 'yes'

frame.args.showerrs then -- if appropriate, show error message error_msg = '

' .. error_msg .. ''; else error_msg = ; -- for concatenation end end

return name .. error_msg; -- return name and error messageend

--|}}where: is the name of the page at http://hnsa.org/hnsa-ships/ (optional) is the name of the ship; if left blank, the template uses the current page title; if a ship name, it is formattedfrom which this code produces: at Historic Naval Ships Association

local function hnsa (frame) local pframe = frame:getParent -- get arguments from calling template frame local ship_name = ; local error_msg = ; local article_title = mw.title.getCurrentTitle.text; -- fetch the article title if not is_set (pframe.args[1]) then return '

missing hsna page'; end local fmt_params = ;

if is_set (pframe.args.showerrs) then -- if showerrs set in template, override showerrs in #invoke: fmt_params.showerrs = pframe.args.showerrs; -- template value else fmt_params.showerrs = frame.args.showerrs; -- invoke value end

if is_set (pframe.args[2]) then fmt_params.name = pframe.args[2]; else fmt_params.name = article_title; -- use article title end

ship_name, error_msg = do_ship_name_format (fmt_params); if is_set (error_msg) and is_set (pframe.args[2]) then -- if unable to format the name local escaped_name = pframe.args[2]:gsub("([%(%)%.%-])", "%%%1"); -- escape some of the Lua magic characters if pframe.args[2]

article_title or -- is name same as article title? nil ~= article_title:find ('%f[%a]' .. escaped_name .. '%f[%s]') or -- is name a word or words substring of article title? nil ~= article_title:find ('%f[%a]' .. escaped_name .. '$') then -- is name a word or words substring that ends article title? ship_name = "" .. pframe.args[2] .. ""; -- non-standard 'name'; perhaps just the name without prefix and dab; error_msg = ; -- unset because we think we have a name end end if is_set (error_msg) and 'yes'

fmt_params.showerrs then error_msg = '

' .. error_msg .. ''; else error_msg = ; -- unset so it doesn't diplay end local output =

return table.concat (output);end

----------------------------< N A V S O U R C E >------------------------------------------------------------

This version of the template was added as a test vehicle for do_ship_name_format.

local function navsource (frame) local pframe = frame:getParent -- get arguments from calling template frame local ship_name = ; local error_msg = ; local article_title = mw.title.getCurrentTitle.text; -- fetch the article title if not is_set (pframe.args[1]) then return '

missing navsource URLcode'; end local fmt_params = ;

if is_set (pframe.args.showerrs) then -- if showerrs set in template, override showerrs in #invoke: fmt_params.showerrs = pframe.args.showerrs; -- template value else fmt_params.showerrs = frame.args.showerrs; -- invoke value end

if is_set (pframe.args[2]) then fmt_params.name = pframe.args[2]; else fmt_params.name = article_title; -- use article title end

ship_name, error_msg = do_ship_name_format (fmt_params); if is_set (error_msg) and is_set (pframe.args[2]) then -- if unable to format the name local escaped_name = pframe.args[2]:gsub("([%(%)%.%-])", "%%%1"); -- escape some of the Lua magic characters if pframe.args[2]

article_title or -- is name same as article title? nil ~= article_title:find ('%f[%a]' .. escaped_name .. '%f[%s]') or -- is name a word or words substring of article title? nil ~= article_title:find ('%f[%a]' .. escaped_name .. '$') then -- is name a word or words substring that ends article title? ship_name = "" .. pframe.args[2] .. ""; -- non-standard 'name'; perhaps just the name without prefix and dab; error_msg = ; -- unset because we think we have a name end end if is_set (error_msg) and 'yes'

fmt_params.showerrs then error_msg = '

' .. error_msg .. ''; else error_msg = ; -- unset so it doesn't diplay end local output =

return table.concat (output);end

----------------------------< _ S H I P >--------------------------------------------------------------------

This is a possible replacement for the template . It has better error detection and handling.

local function _ship (prefix, name, dab, control, unlinked_prefix, unlinked_whole) local error_msg = ; local category = ; if not is_set (control) then control = ; -- if not provided, ensure that control is empty string for comparisons elseif control:find ('%-') then -- shortcut for |link=no when using a format control parameter ...|SSBN-659|-6}} same as ...|SSBN-659|6|link=no}} unlinked_whole = true; -- set the unlinked flag control = control:match ('%d'); -- strip out the hyphen end -- dispose of error conditions straight away if not is_set (name) then -- this is the only required parameter error_msg = ' missing name'; elseif not is_set (dab) and ('1'

control or '3'

control or '5'

control) then -- dab required when control value set to expect it error_msg = ' missing disambiguator'; elseif not is_set (prefix) and ('5'

control or '6'

control) then -- prefix required when control value set to expect it error_msg = ' missing prefix'; elseif '4'

control then -- displaying only the prefix error_msg = 'invalid control parameter: ' .. control; elseif is_set (control) then if ('number' ~= type (tonumber (control))) or (1 > tonumber (control) or 6 < tonumber (control)) then -- control must be a number between 1 through 6 error_msg = 'invalid control parameter: ' .. control; end elseif not is_set (prefix) and unlinked_prefix then -- prefix required when |up=yes error_msg = ' missing prefix'; end if is_set (error_msg) then if 0

mw.title.getCurrentTitle.namespace then -- only categorize pages in article space category = ''; end

return '

' .. error_msg .. '' .. category; -- return an error message; don't bother with making a link end

local link_name; local link = '' .. link_name .. ''; endend

--ship|||||link=|up=}}

local function ship (frame) -- this version not supported from the template yet local prefix = mw.text.trim (frame.args[1] or ); -- fetch positional parameters into named variables for readability local name = mw.text.trim (frame.args[2] or ); -- stripped of leading and trailing whitespace local dab = mw.text.trim (frame.args[3] or ); -- and and set to empty string (is that needed?) local control = frame.args[4]; local unlinked_prefix = 'yes'

frame.args.up; local unlinked_whole = 'no'

frame.args.link; return _ship (prefix, name, dab, control, unlinked_prefix, unlinked_whole);end

--

local function list_error (prefix, message, sep, param_val, showerrs) local err_msg = '%s

list error: %s (help)%s%s%s'; local category = ;

if 0

mw.title.getCurrentTitle.namespace then -- only categorize pages in article space category = ''; end if true

showerrs then return string.format (err_msg, prefix, message, sep, param_val, category); -- put it all together else return param_val .. category; endend

--

The above renders without proper indents for items marked ** and ***.

If the list is not wrapped in

then the above list is rendered with bullets which is contrary to the Infobox ship usage guide.

This code translates a bulleted list into an html unordered list:

There are rules: 1. The parameter value must begin with a splat but may have leading and trailing whitespace. 2. Each list item after the first must begin on a new line just as is required by normal bulleted lists. 3. When adding a sublevel, the number of splats may increase by one and never more. This is illegal: *item ***itemWhen any of these rules are violated, unbulleted_list returns the original text and adds the articleto Category:WPSHIPS:Infobox list errors. Error messaging in this function is somewhat sketchy so they aredisabled. After initial adoption, better error messaging could/should be implemented.

This function receives the content of one parameter:

local function _unbulleted_list (param) local showerrs = true; -- set to false to hide error messages local List_item_otag = '

  • '; -- hanging indent markup; everything moves right with padding-left; first line moved left by neg indent if nil

    param:match ('^%s*%*') then -- parameter value must begin with a splat (ignoring leading white space) if param:match ('<[%s/]*[Bb][Rr][%s/]*>') then -- if the parameter value has a list using variants of
    tag return list_error (, '<br /> list', '
    ', param, showerrs); -- return an error message with maintenance category elseif param:match ('

    ', param, showerrs); -- return an error message with maintenance category elseif param:match ('.+\n%*') then -- if the parameter value has text followed by an unordered list return list_error (, 'mixed text and list', '
    ', param, showerrs); -- return an error message with maintenance category end return param; -- return the parameter as is end local item_table = mw.text.split (mw.text.trim (param), '\n'); -- trim white space from end then make a table of bulleted items by splitting on newlines if 1

    #item_table then -- if only one bulleted item, no need for a list return (item_table[1]:gsub ('^%*%s*', )); -- trim off the splat and any following white space and done end if item_table[1]:match ('^%*%*+') then -- if first list item uses more than one splat, that's an error return list_error ('*', 'too many * at start of list', '\n', param, showerrs); -- return an error message with maintenance category end local html_table = ; -- table to hold the html output local level = 1; -- used to indicate when a new
      is required local splats = 0; -- number of splats that start each item in the list local item = ; -- the item text table.insert (html_table, '
        ') -- this for first
          tag; sets no bullets and no indent for _,v in ipairs (item_table) do splats, item = v:match ('(%*+)%s*(.*)'); -- split the item into splats and item text if nil

    splats then -- nil if there is an extra line between items return list_error ('*', 'list item missing markup', '\n', param, showerrs); -- return an error message with maintenance category elseif

    item then return list_error ('*', 'empty list item', '\n', param, showerrs); -- return an error message with maintenance category elseif item:match ('^[;:]') then -- if the list item is mixed unordered list / description list markup (*:) return list_error ('*', 'mixed list type', '\n', param, showerrs); -- return an error message with maintenance category end

    splats = splats:len; -- change string of splats into a number indicating how many splats there are if splats

    level then -- if at the same level as previous item table.insert (html_table, List_item_otag .. item .. '

  • '); elseif splats

    level + 1 then -- number of splats can only increase by one level = splats; -- record the new level table.insert (html_table, '

    '); -- close each sub '); -- close each