# Module:Detect singular

local p = {}
local getArgs = require('Module:Arguments').getArgs
local yesNo = require('Module:Yesno')
local getPlain = require('Module:Text').Text.getPlain  
-- function to determine whether "sub" occurs in "s"
local function plainFind(s, sub)
return mw.ustring.find(s, sub, 1, true)
end  
-- function to count the number of times "pattern" (a regex) occurs in "s"
local function countMatches(s, pattern)
local _, count = mw.ustring.gsub(s, pattern, '')
return count
end  
local singular = 1
local likelyPlural = 2
local plural = 3  
-- Determine whether a string is singular or plural (i.e., it represents one
-- item or many)
-- Arguments:
-- origArgs[1]: string to process
-- origArgs.no_comma: if false, use commas to detect plural (default false)
-- origArgs.parse_links: if false, treat wikilinks as opaque singular objects (default false)
-- Returns:
-- singular, likelyPlural, or plural (see constants above), or nil for completely unknown
function p._main(origArgs)
origArgs = type(origArgs) == 'table' and origArgs or {}
local args = {}
-- canonicalize boolean arguments
for key, default in pairs({no_comma=false,parse_links=false,any_comma=false,
no_and=false,parse_number=false}) do
if origArgs[key] == nil then
args[key] = default
else
args[key] = yesNo(origArgs[key],default)
end
end
local checkComma = not args.no_comma
local checkAnd = not args.no_and
local rewriteLinks = not args.parse_links
local anyComma = args.any_comma
local parseNumber = args.parse_number
local s = origArgs[1] -- the input string
if not s then
return nil -- empty input returns nil
end
s = tostring(s)
s = mw.text.decode(s,true) --- replace HTML entities (to avoid spurious semicolons)
if plainFind(s,'data-plural="0"') then -- magic data string to return true
return singular
end
if plainFind(s,'data-plural="1"') then -- magic data string to return false
return plural
end
-- count number of list items
local numListItems = countMatches(s,'<%s*li')
-- if exactly one, then singular, if more than one, then plural
if numListItems == 1 then
return singular
end
if numListItems > 1 then
return plural
end
-- if "list of" occurs inside of wlink, then it's plural
if mw.ustring.find(s:lower, '%[%^%*list of[^%]]+%]%]') then
return plural
end
if parseNumber then
local m = tonumber(mw.ustring.match(s,"^%s*(%d+)"))
if m then
return m > 1 and plural or singular
end
end
-- fix for trailing br tags passed through template:marriage
s = mw.ustring.gsub(s, '<%s*br[^>]*>%s*(</div>)', '%1')
-- replace all wikilinks with fixed string
if rewriteLinks then
s = mw.ustring.gsub(s,'%b[]','WIKILINK')
end
-- Five conditions: any one of them can make the string a likely plural or plural
local hasBreak = mw.ustring.find(s,'<%s*br')
-- For the last 4, evaluate on string stripped of wikimarkup
s = getPlain(s)
local hasBullets = countMatches(s,'%*+') > 1
local multipleQids = mw.ustring.find(s,'Q%d+[%p%s]+Q%d+') -- has multiple QIDs in a row
if hasBullets or multipleQids then
return plural
end
local commaPattern = anyComma and '[,;]' or '%D[,;]%D' -- semi-colon similar to comma
local hasComma = checkComma and mw.ustring.find(s, commaPattern)
local hasAnd = checkAnd and mw.ustring.find(s,'[,%s]and%s')
if hasBreak or hasComma or hasAnd then
return likelyPlural
end
return singular
end  
function p._pluralize(args)
args = type(args) == 'table' and args or {}
local singularForm = args[3] or args.singular or ""
local pluralForm = args[4] or args.plural or ""
local likelyForm = args.likely or pluralForm
local link = args[5] or args.link
if link then
link = tostring(link)
singularForm = *..singularForm..*
pluralForm = *..pluralForm..*
likelyForm = *..likelyForm..*
end
if args[2] then
return pluralForm
end
local detect = p._main(args)
if detect == nil then
return "" -- return blank on complete failure
end
if detect == singular then
return singularForm
elseif detect == likelyPlural then
return likelyForm
else
return pluralForm
end
end  
function p.main
local args = getArgs
-- For template, return 1 if singular, blank if plural or empty
local result = p._main(args)
if result == nil then
return 1
end
return result == singular and 1 or ""
end  
function p.pluralize
local args = getArgs
return p._pluralize(args)
end  
return p