require ('strict');
local p = {}  
p.trim = function
return mw.text.trim(.args[1] or "")
end  
p.sentence = function
-- is strip-marker safe, string.lower is not.
.args[1] = :callParserFunction('lc', .args[1])
return p.ucfirst
end  
p.ucfirst = function
local s = .args[1];
if not s or '' == s or s:match ('^%s+$') then -- when 
 is nil, empty, or only whitespace
return s; -- abandon because nothing to do
end  
s = mw.text.trim( .args[1] or "" )
local s1 = ""  
local prefix_patterns_t = { -- sequence of prefix patterns
'^\127[^\127]*UNIQ%-%-%a+%-%x+%-QINU[^\127]*\127', -- stripmarker
'^([%*;:#]+)', -- various list markup
'^(\'\'\'*)', -- bold / italic markup
'^(%b<>)', -- html-like tags because some templates render these
'^(&%a+;)', -- html character entities because some templates render these
'^(&#%d+;)', -- html numeric (decimal) entities because some templates render these
'^(&#x%x+;)', -- html numeric (hexadecimal) entities because some templates render these
'^(%s+)', -- any whitespace characters
'^([%(%)%-%+%?%.%%!~!@%$%^&_={}/`,‘’„“”ʻ|\"\'\\]+)', -- miscellaneous punctuation
}  
local prefixes_t = {}; -- list, bold/italic, and html-like markup, & whitespace saved here  
local function prefix_strip (s) -- local function to strip prefixes from 

for _, pattern in ipairs (prefix_patterns_t) do -- spin through <prefix_patterns_t>
if s:match (pattern) then -- when there is a match
local prefix = s:match (pattern); -- get a copy of the matched prefix
table.insert (prefixes_t, prefix); -- save it
s = s:sub (prefix:len + 1); -- remove the prefix from 

return s, true; -- return 
 without prefix and flag; force restart at top of sequence because misc punct removal can break stripmarker
end
end
return s; -- no prefix found; return 
 with nil flag
end  
local prefix_removed; -- flag; boolean true as long as prefix_strip finds and removes a prefix  
repeat -- one by one remove list, bold/italic, html-like markup, whitespace, etc from start of 

s, prefix_removed = prefix_strip (s);
until (not prefix_removed); -- until <prefix_removed> is nil  
s1 = table.concat (prefixes_t); -- recreate the prefix string for later reattachment  
local first_text = mw.ustring.match (s, '^%[%^%+%]%]'); -- extract wikilink at start of string if present; TODO: this can be string.match?  
local upcased;
if first_text then
if first_text:match ('^%[%[[^|]+|[^%]]+%]%]') then -- if <first_text> is a piped link
upcased = mw.ustring.match (s, '^%[%[[^|]+|%W*(%w)'); -- get first letter character
upcased = mw.ustring.upper (upcased); -- upcase first letter character
s = mw.ustring.gsub (s, '^(%[%[[^|]+|%W*)%w', '%1' .. upcased); -- replace
else -- here when <first_text> is a wikilink but not a piped link
upcased = mw.ustring.match (s, '^%[%[%W*%w'); -- get '' and first letter
upcased = mw.ustring.upper (upcased); -- upcase first letter character
s = mw.ustring.gsub (s, '^%[%[%W*%w', upcased); -- replace; no capture needed here
end  
elseif s:match ('^%[%S+%s+[^%+%]') then -- if 
 is a ext link of some sort; must have label text
upcased = mw.ustring.match (s, '^%[%S+%s+%W*(%w)'); -- get first letter character
upcased = mw.ustring.upper (upcased); -- upcase first letter character
s = mw.ustring.gsub (s, '^(%[%S+%s+%W*)%w', '%1' .. upcased); -- replace  
elseif s:match ('^%[%S+%s*%]') then -- if 
 is a ext link without label text; nothing to do
return s1 .. s; -- reattach prefix string (if present) and done  
else -- 
 is not a wikilink or ext link; assume plain text
upcased = mw.ustring.match (s, '^%W*%w'); -- get the first letter character
upcased = mw.ustring.upper (upcased); -- upcase first letter character
s = mw.ustring.gsub (s, '^%W*%w', upcased); -- replace; no capture needed here
end  
return s1 .. s; -- reattach prefix string (if present) and done
end  
p.title = function
-- http://grammar.yourdictionary.com/capitalization/rules-for-capitalization-in-titles.html
-- recommended by The U.S. Government Printing Office Style Manual:
-- "Capitalize all words in titles of publications and documents,
-- except a, an, the, at, by, for, in, of, on, to, up, and, as, but, or, and nor."
local alwayslower = {['a'] = 1, ['an'] = 1, ['the'] = 1,
['and'] = 1, ['but'] = 1, ['or'] = 1, ['for'] = 1,
['nor'] = 1, ['on'] = 1, ['in'] = 1, ['at'] = 1, ['to'] = 1,
['from'] = 1, ['by'] = 1, ['of'] = 1, ['up'] = 1 }
local res = ''
local s = mw.text.trim( .args[1] or "" )
local words = mw.text.split( s, " ")
for i, s in ipairs(words) do
-- is strip-marker safe, string.lower is not.
s = :callParserFunction('lc', s)
if i == 1 or alwayslower[s] ~= 1 then
s = mw.getContentLanguage:ucfirst(s)
end
words[i] = s
end
return table.concat(words, " ")
end  
-- findlast finds the last item in a list
-- the first unnamed parameter is the list
-- the second, optional unnamed parameter is the list separator (default = comma space)
-- returns the whole list if separator not found
p.findlast = function
local s = mw.text.trim( .args[1] or "" )
local sep = .args[2] or ""
if sep == "" then sep = ", " end
local pattern = ".*" .. sep .. "(.*)"
local a, b, last = s:find(pattern)
if a then
return last
else
return s
end
end  
-- stripZeros finds the first number and strips leading zeros (apart from units)
-- e.g "0940" -> "940"; "Year: 0023" -> "Year: 23"; "00.12" -> "0.12"
p.stripZeros = function
local s = mw.text.trim(.args[1] or "")
local n = tonumber( string.match( s, "%d+" ) ) or ""
s = string.gsub( s, "%d+", n, 1 )
return s
end  
-- nowiki ensures that a string of text is treated by the MediaWiki software as just a string
-- it takes an unnamed parameter and trims whitespace, then removes any wikicode
p.nowiki = function
local str = mw.text.trim(.args[1] or "")
return mw.text.nowiki(str)
end  
-- split splits text at boundaries specified by separator
-- and returns the chunk for the index idx (starting at 1)
-- #invoke:String2 |split |text |separator |index |true/false
-- #invoke:String2 |split |txt=text |sep=separator |idx=index |plain=true/false
-- if plain is false/no/0 then separator is treated as a Lua pattern - defaults to plain=true
p.split = function
local args = .args
if not(args[1] or args.txt) then args = :getParent.args end
local txt = args[1] or args.txt or ""
if txt == "" then return nil end
local sep = (args[2] or args.sep or ""):gsub('"', '')
local idx = tonumber(args[3] or args.idx) or 1
local plain = (args[4] or args.plain or "true"):sub(1,1)
plain = (plain ~= "f" and plain ~= "n" and plain ~= "0")
local splittbl = mw.text.split( txt, sep, plain )
if idx < 0 then idx = #splittbl + idx + 1 end
return splittbl[idx]
end  
-- val2percent scans through a string, passed as either the first unnamed parameter or |txt=
-- it converts each number it finds into a percentage and returns the resultant string.
p.val2percent = function
local args = .args
if not(args[1] or args.txt) then args = :getParent.args end
local txt = mw.text.trim(args[1] or args.txt or "")
if txt == "" then return nil end
local function v2p (x)
x = (tonumber(x) or 0) * 100
if x == math.floor(x) then x = math.floor(x) end
return x .. "%"
end
txt = txt:gsub("%d[%d%.]*", v2p) -- store just the string
return txt
end  
-- one2a scans through a string, passed as either the first unnamed parameter or |txt=
-- it converts each occurrence of 'one ' into either 'a ' or 'an ' and returns the resultant string.
p.one2a = function
local args = .args
if not(args[1] or args.txt) then args = :getParent.args end
local txt = mw.text.trim(args[1] or args.txt or "")
if txt == "" then return nil end
txt = txt:gsub(" one ", " a "):gsub("^one", "a"):gsub("One ", "A "):gsub("a ([aeiou])", "an %1"):gsub("A ([aeiou])", "An %1")
return txt
end  
-- findpagetext returns the position of a piece of text in a page
-- First positional parameter or |text is the search text
-- Optional parameter |title is the page title, defaults to current page
-- Optional parameter |plain is either true for plain search (default) or false for Lua pattern search