• Welcome to the Kancolle Wiki!
  • If you have any questions regarding site content, account registration, etc., please visit the KanColle Wiki Discord

Difference between revisions of "Module:GetArgs"

From Kancolle Wiki
Jump to navigation Jump to search
com>Ckwng
(Testing if Scribunto works, 'cause templates are really stupid =()
 
m (4 revisions imported)
 
(3 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
--[[
 
--[[
  Handles most of the boilerplate necessary to fetch arguments, such as trimming
+
Handles most of the boilerplate necessary to fetch arguments, such as trimming
  leading/trailing whitespace, making blank arguments evaluate to false, correctly
+
leading/trailing whitespace, making blank arguments evaluate to false, correctly
  choosing between current-frame and parent-frame, etc.
+
choosing between current-frame and parent-frame, etc.
+
 
  Originally written on the English Wikipedia by Mr. Stradivarius, Jackmcbarn, and Anomie.
+
Originally written on the English Wikipedia by Mr. Stradivarius, Jackmcbarn, and Anomie.
+
 
  Code released under the GPL v2+ as per:
+
Code released under the GPL v2+ as per:
  https://en.wikipedia.org/w/index.php?diff=624020648&oldid=624019645
+
https://en.wikipedia.org/w/index.php?diff=624020648&oldid=624019645
  https://en.wikipedia.org/w/index.php?diff=624073793&oldid=624020648
+
https://en.wikipedia.org/w/index.php?diff=624073793&oldid=624020648
+
 
  @license GNU GPL v2+
+
@license GNU GPL v2+
 
]]
 
]]
+
 
 
local libraryUtil = require( 'libraryUtil' )
 
local libraryUtil = require( 'libraryUtil' )
+
 
 
local sandboxSuffixPattern = string.format( '/%s$',
 
local sandboxSuffixPattern = string.format( '/%s$',
  mw.message.new( 'scribunto-template-sandbox-subpage-name' ):plain():gsub("%p", "%%%0")
+
mw.message.new( 'scribunto-template-sandbox-subpage-name' ):plain():gsub("%p", "%%%0")
 
)
 
)
+
 
-- Generate four different tidyVal functions, so that we don't have to check the options every
 
-- time we call it.
 
 
 
local function tidyValDefault( k, v )
 
local function tidyValDefault( k, v )
  if type( v ) == 'string' then
+
if type( v ) == 'string' then
      v = v:match( '^%s*(.-)%s*$' )
+
v = v:match( '^%s*(.-)%s*$' )
      if v == '' then
+
if v == '' then
          return nil
+
return nil
      end
+
end
  end
+
end
  return v
+
return v
 
end
 
end
+
 
 
local function tidyValTrimOnly( k, v )
 
local function tidyValTrimOnly( k, v )
  if type( v ) == 'string' then
+
if type( v ) == 'string' then
      return v:match( '^%s*(.-)%s*$' )
+
return v:match( '^%s*(.-)%s*$' )
  else
+
else
      return v
+
return v
  end
+
end
 
end
 
end
+
 
 
local function tidyValRemoveBlanksOnly( k, v )
 
local function tidyValRemoveBlanksOnly( k, v )
  if type( v ) == 'string' and not v:find( '%S' ) then
+
if type( v ) == 'string' and not v:find( '%S' ) then
      return nil
+
return nil
  else
+
else
      return v
+
return v
  end
+
end
 
end
 
end
+
 
 
local function tidyValNoChange( k, v )
 
local function tidyValNoChange( k, v )
  return v
+
return v
 
end
 
end
+
 
 +
local function matchesTitle( given, title )
 +
local tp = type( given )
 +
return ( tp == 'string' or tp == 'number' ) and mw.title.new( given ).prefixedText == title
 +
end
 +
 
 +
-- Checks whether title matches any wrapper templates that were specified by
 +
-- the user.
 +
local function titleIsWrapper( title, wrappers )
 +
title = title:gsub( sandboxSuffixPattern, '' )
 +
if matchesTitle( wrappers, title ) then
 +
return true
 +
elseif type( wrappers ) == 'table' then
 +
-- If either a key or a value matches, it's a match
 +
if wrappers[title] ~= nil then
 +
return true
 +
else
 +
for _,v in ipairs( wrappers ) do
 +
if matchesTitle( v, title ) then
 +
return true
 +
end
 +
end
 +
end
 +
end
 +
return false
 +
end
 +
 
 
return function( options )
 
return function( options )
  libraryUtil.checkType( 'getArgs', 1, options, 'table', true )
+
libraryUtil.checkType( 'getArgs', 1, options, 'table', true )
  local frame = options.frame
+
local frame = options.frame
  if frame == nil then
+
if frame == nil then
      return {}
+
return {}
  end
+
end
  libraryUtil.checkTypeForNamedArg( 'getArgs', 'frame', frame, 'table' )
+
libraryUtil.checkTypeForNamedArg( 'getArgs', 'frame', frame, 'table' )
+
 
  --[[
+
--[[
  -- Get the argument tables. If we were passed a valid frame object, see if we're being
+
-- Get the tables to look up arguments from. These could be any of:
  -- called via a wrapper. If so, then get pargs, the wrapper's arguments (and if
+
-- * the frame arguments
  -- wrappersUseFrame is set, the frame's arguments as well). Otherwise, just get fargs,
+
-- * the parent frame arguments
  -- the frame's arguments. If we weren't passed a valid frame object, we are being called
+
-- * a table of arguments passed from a Lua module or from the debug console
  -- from another Lua module or from the debug console, so assume that we were passed a
+
-- We try to select the correct one(s) based on whether we were passed a
  -- table of args directly, and assign it to a new variable (luaArgs).
+
-- valid frame object and whether we are being called from a wrapper
  --]]
+
-- template. Usually we only use one of these tables, but if
  local fargs, pargs, luaArgs
+
-- options.wrappersUseFrame is set we may use both the frame arguments and
  if type( frame.args ) == 'table' and type( frame.getParent ) == 'function' then
+
-- the parent frame arguments.
      if options.wrappers then
+
--]]
          local parent = frame:getParent()
+
local argTables = {}
          local found = false
+
if type( frame.args ) == 'table' and type( frame.getParent ) == 'function' then
          if parent then
+
-- We were passed a valid frame. Find out if the title of its parent
              local title = parent:getTitle():gsub( sandboxSuffixPattern, '' )
+
-- frame is contained in options.wrappers.
              if options.wrappers == title then
+
if options.wrappers then
                  found = true
+
local parent = frame:getParent()
              elseif type( options.wrappers ) == 'table' then
+
if parent and titleIsWrapper( parent:getTitle(), options.wrappers ) then
                  -- If either a key or a value matches, it's a match
+
argTables[1] = parent.args
                  if options.wrappers[title] ~= nil then
+
end
                      found = true
+
end
                  else
+
if not argTables[1] or options.wrappersUseFrame then
                      for _,v in ipairs( options.wrappers ) do
+
table.insert( argTables, frame.args )
                          if v == title then
+
end
                              found = true
+
else
                              break
+
-- Assume frame is a table of arguments passed from a Lua module or
                          end
+
-- from the debug console.
                      end
+
argTables[1] = frame
                  end
+
end
              end
+
 
+
-- Get tidyVal, the function that we use to trim whitespace and remove
              if found then
+
-- blank arguments. This can be set by the user with options.valueFunc.
                  pargs = parent.args
+
-- Otherwise it is generated from options.trim and options.removeBlanks.
              end
+
-- We define four separate default tidyVal functions so that we can avoid
          end
+
-- checking options.trim and options.removeBlanks every time we look up an
+
-- argument.
          if not found or options.wrappersUseFrame then
+
local tidyVal = options.valueFunc
              fargs = frame.args
+
if tidyVal then
          end
+
libraryUtil.checkTypeForNamedArg( 'getArgs', 'valueFunc', tidyVal, 'function' )
      else
+
elseif options.trim ~= false then
          fargs = frame.args
+
if options.removeBlanks ~= false then
      end
+
tidyVal = tidyValDefault
  else
+
else
      luaArgs = frame
+
tidyVal = tidyValTrimOnly
  end
+
end
+
else
  -- Set the order of precedence of the argument tables. If the variables are nil, nothing
+
if options.removeBlanks ~= false then
  -- will be added to the table, which is how we avoid clashes between the frame/parent
+
tidyVal = tidyValRemoveBlanksOnly
  -- args and the Lua args.
+
else
  local argTables = { pargs }
+
tidyVal = tidyValNoChange
  argTables[#argTables + 1] = fargs
+
end
  argTables[#argTables + 1] = luaArgs
+
end
+
 
  -- Generate the tidyVal function. If it has been specified by the user, we use that; if
+
-- Set up a metatable to allow transparent fetching of arguments from argTables
  -- not, we choose one of four functions depending on the options chosen. This is so that
+
-- * mt - the metatable
  -- we don't have to call the options table every time the function is called.
+
-- * fetchedArgs - a table to store memoized arguments fetched from argTables
  local tidyVal = options.valueFunc
+
-- * nilArgs - a table to memoize nil arguments
  if tidyVal then
+
-- * donePairs - whether pairs has been run
      libraryUtil.checkTypeForNamedArg( 'getArgs', 'valueFunc', tidyVal, 'function' )
+
-- * doneIpairs - whether ipairs has been run
  elseif options.trim ~= false then
+
local mt, fetchedArgs, nilArgs = {}, {}, {}
      if options.removeBlanks ~= false then
+
local donePairs, doneIpairs = false, false
          tidyVal = tidyValDefault
+
 
      else
+
-- This function merges arguments from argument tables into fetchedArgs,
          tidyVal = tidyValTrimOnly
+
-- Earlier argument tables take precedence over later ones; once a value
      end
+
-- is written it is not overwritten.
  else
+
local function mergeArgs( tables )
      if options.removeBlanks ~= false then
+
for _, t in ipairs( tables ) do
          tidyVal = tidyValRemoveBlanksOnly
+
for key, val in pairs( t ) do
      else
+
if fetchedArgs[key] == nil and not nilArgs[key] then
          tidyVal = tidyValNoChange
+
local tidiedVal = tidyVal( key, val )
      end
+
if tidiedVal == nil then
  end
+
nilArgs[key] = true
+
else
  -- Set up the args, fetchedArgs and nilArgs tables. args will be the one accessed from
+
fetchedArgs[key] = tidiedVal
  -- functions, and fetchedArgs will hold the actual arguments. Nil arguments are memoized
+
end
  -- in nilArgs, and args_mt connects all of them together.
+
end
  local args, fetchedArgs, nilArgs, args_mt = {}, {}, {}, {}
+
end
  local donePairs, doneIpairs = false, false
+
end
+
end
  local function mergeArgs( iterator, tables )
+
 
      -- Accepts multiple tables as input and merges their keys and values into one
+
function mt.__index( t, key )
      -- table using the specified iterator. If a value is already present, it is not
+
local val = fetchedArgs[key]
      -- overwritten; tables listed earlier have precedence. We are also memoizing nil
+
if val ~= nil then
      -- values, but those values can be overwritten.
+
return val
      for _, t in ipairs( tables ) do
+
elseif donePairs or nilArgs[key] then
          for key, val in iterator( t ) do
+
-- If pairs has been run we already have all the arguments in
              if fetchedArgs[key] == nil then
+
-- fetchedArgs.
                  local tidiedVal = tidyVal( key, val )
+
return nil
                  if tidiedVal == nil then
+
end
                      nilArgs[key] = true
+
for _, argTable in ipairs( argTables ) do
                  else
+
local argTableVal = tidyVal( key, argTable[key] )
                      fetchedArgs[key] = tidiedVal
+
if argTableVal ~= nil then
                  end
+
fetchedArgs[key] = argTableVal
              end
+
return argTableVal
          end
+
end
      end
+
end
  end
+
nilArgs[key] = true
+
return nil
  -- Define metatable behaviour. Arguments are memoized in the fetchedArgs table, and are
+
end
  -- only fetched from the argument tables once. Fetching arguments from the argument
+
 
  -- tables is the most resource-intensive step in this module, so we try and avoid it
+
function mt.__newindex( t, key, val )
  -- where possible. For this reason, nil arguments are also memoized, in the nilArgs
+
if val == nil then
  -- table. Also, we keep a record in the metatable of when pairs and ipairs have been
+
-- We need to memoize the nil so that we don't look up the key in
  -- called, so we do not run pairs and ipairs on the argument tables more than once. We
+
-- the argument tables if it is accessed again.
  -- also do not run ipairs on fargs and pargs if pairs has already been run, as all the
+
nilArgs[key] = true
  -- arguments will already have been copied over.
+
end
+
fetchedArgs[key] = val
  function args_mt.__index( t, key )
+
end
      -- Fetches an argument when the args table is indexed. First we check to see if
+
 
      -- the value is memoized, and if not we try and fetch it from the argument
+
function mt.__pairs()
      -- tables. When we check memoization, we need to check fetchedArgs before
+
if not donePairs then
      -- nilArgs, as both can be non-nil at the same time. If the argument is not
+
donePairs = true
      -- present in fetchedArgs, we also check whether pairs has been run yet. If
+
mergeArgs( argTables )
      -- pairs has already been run, we return nil. This is because all the arguments
+
end
      -- will have already been copied into fetchedArgs by the mergeArgs function,
+
return pairs( fetchedArgs )
      -- meaning that any other arguments must be nil.
+
end
      local val = fetchedArgs[key]
+
 
      if val ~= nil then
+
local function inext(t, i)
          return val
+
-- This uses our __index metamethod
      elseif donePairs or nilArgs[key] then
+
local v = t[i + 1]
          return nil
+
if v ~= nil then
      end
+
return i + 1, v
      for _, argTable in ipairs( argTables ) do
+
end
          local argTableVal = tidyVal( key, argTable[key] )
+
end
          if argTableVal == nil then
+
 
              nilArgs[key] = true
+
function mt.__ipairs(t)
          else
+
return inext, t, 0
              fetchedArgs[key] = argTableVal
+
end
              return argTableVal
+
 
          end
+
return setmetatable( {}, mt )
      end
 
      return nil
 
  end
 
 
  function args_mt.__newindex( t, key, val )
 
      if val == nil then
 
          -- If the argument is to be overwritten with nil, we need to memoize the
 
          -- nil in nilArgs, so that the value isn't looked up in the argument
 
          -- tables if it is accessed again.
 
          nilArgs[key] = true
 
      end
 
      fetchedArgs[key] = val
 
  end
 
 
  function args_mt.__pairs()
 
      if not donePairs then
 
          donePairs = true
 
          mergeArgs( pairs, argTables )
 
      end
 
      return pairs( fetchedArgs )
 
  end
 
 
  function args_mt.__ipairs()
 
      if not doneIpairs and not donePairs then
 
          doneIpairs = true
 
          mergeArgs( ipairs, argTables )
 
      end
 
      return ipairs( fetchedArgs )
 
  end
 
 
  return setmetatable( args, args_mt )
 
 
end
 
end

Latest revision as of 11:50, 12 May 2021

Documentation for this module may be created at Module:GetArgs/doc

--[[
	Handles most of the boilerplate necessary to fetch arguments, such as trimming
	leading/trailing whitespace, making blank arguments evaluate to false, correctly
	choosing between current-frame and parent-frame, etc.

	Originally written on the English Wikipedia by Mr. Stradivarius, Jackmcbarn, and Anomie.

	Code released under the GPL v2+ as per:
	https://en.wikipedia.org/w/index.php?diff=624020648&oldid=624019645
	https://en.wikipedia.org/w/index.php?diff=624073793&oldid=624020648

	@license GNU GPL v2+
]]

local libraryUtil = require( 'libraryUtil' )

local sandboxSuffixPattern = string.format( '/%s$',
	mw.message.new( 'scribunto-template-sandbox-subpage-name' ):plain():gsub("%p", "%%%0")
)

local function tidyValDefault( k, v )
	if type( v ) == 'string' then
		v = v:match( '^%s*(.-)%s*$' )
		if v == '' then
			return nil
		end
	end
	return v
end

local function tidyValTrimOnly( k, v )
	if type( v ) == 'string' then
		return v:match( '^%s*(.-)%s*$' )
	else
		return v
	end
end

local function tidyValRemoveBlanksOnly( k, v )
	if type( v ) == 'string' and not v:find( '%S' ) then
		return nil
	else
		return v
	end
end

local function tidyValNoChange( k, v )
	return v
end

local function matchesTitle( given, title )
	local tp = type( given )
	return ( tp == 'string' or tp == 'number' ) and mw.title.new( given ).prefixedText == title
end

-- Checks whether title matches any wrapper templates that were specified by
-- the user.
local function titleIsWrapper( title, wrappers )
	title = title:gsub( sandboxSuffixPattern, '' )
	if matchesTitle( wrappers, title ) then
		return true
	elseif type( wrappers ) == 'table' then
		-- If either a key or a value matches, it's a match
		if wrappers[title] ~= nil then
			return true
		else
			for _,v in ipairs( wrappers ) do
				if matchesTitle( v, title ) then
					return true
				end
			end
		end
	end
	return false
end

return function( options )
	libraryUtil.checkType( 'getArgs', 1, options, 'table', true )
	local frame = options.frame
	if frame == nil then
		return {}
	end
	libraryUtil.checkTypeForNamedArg( 'getArgs', 'frame', frame, 'table' )

	--[[
	-- Get the tables to look up arguments from. These could be any of:
	-- * the frame arguments
	-- * the parent frame arguments
	-- * a table of arguments passed from a Lua module or from the debug console
	-- We try to select the correct one(s) based on whether we were passed a
	-- valid frame object and whether we are being called from a wrapper
	-- template. Usually we only use one of these tables, but if
	-- options.wrappersUseFrame is set we may use both the frame arguments and
	-- the parent frame arguments.
	--]]
	local argTables = {}
	if type( frame.args ) == 'table' and type( frame.getParent ) == 'function' then
		-- We were passed a valid frame. Find out if the title of its parent
		-- frame is contained in options.wrappers.
		if options.wrappers then
			local parent = frame:getParent()
			if parent and titleIsWrapper( parent:getTitle(), options.wrappers ) then
				argTables[1] = parent.args
			end
		end
		if not argTables[1] or options.wrappersUseFrame then
			table.insert( argTables, frame.args )
		end
	else
		-- Assume frame is a table of arguments passed from a Lua module or
		-- from the debug console.
		argTables[1] = frame
	end

	-- Get tidyVal, the function that we use to trim whitespace and remove
	-- blank arguments. This can be set by the user with options.valueFunc.
	-- Otherwise it is generated from options.trim and options.removeBlanks.
	-- We define four separate default tidyVal functions so that we can avoid
	-- checking options.trim and options.removeBlanks every time we look up an
	-- argument.
	local tidyVal = options.valueFunc
	if tidyVal then
		libraryUtil.checkTypeForNamedArg( 'getArgs', 'valueFunc', tidyVal, 'function' )
	elseif options.trim ~= false then
		if options.removeBlanks ~= false then
			tidyVal = tidyValDefault
		else
			tidyVal = tidyValTrimOnly
		end
	else
		if options.removeBlanks ~= false then
			tidyVal = tidyValRemoveBlanksOnly
		else
			tidyVal = tidyValNoChange
		end
	end

	-- Set up a metatable to allow transparent fetching of arguments from argTables
	-- * mt - the metatable
	-- * fetchedArgs - a table to store memoized arguments fetched from argTables
	-- * nilArgs - a table to memoize nil arguments
	-- * donePairs - whether pairs has been run
	-- * doneIpairs - whether ipairs has been run
	local mt, fetchedArgs, nilArgs = {}, {}, {}
	local donePairs, doneIpairs = false, false

	-- This function merges arguments from argument tables into fetchedArgs,
	-- Earlier argument tables take precedence over later ones; once a value
	-- is written it is not overwritten.
	local function mergeArgs( tables )
		for _, t in ipairs( tables ) do
			for key, val in pairs( t ) do
				if fetchedArgs[key] == nil and not nilArgs[key] then
					local tidiedVal = tidyVal( key, val )
					if tidiedVal == nil then
						nilArgs[key] = true
					else
						fetchedArgs[key] = tidiedVal
					end
				end
			end
		end
	end

	function mt.__index( t, key )
		local val = fetchedArgs[key]
		if val ~= nil then
			return val
		elseif donePairs or nilArgs[key] then
			-- If pairs has been run we already have all the arguments in
			-- fetchedArgs.
			return nil
		end
		for _, argTable in ipairs( argTables ) do
			local argTableVal = tidyVal( key, argTable[key] )
			if argTableVal ~= nil then
				fetchedArgs[key] = argTableVal
				return argTableVal
			end
		end
		nilArgs[key] = true
		return nil
	end

	function mt.__newindex( t, key, val )
		if val == nil then
			-- We need to memoize the nil so that we don't look up the key in
			-- the argument tables if it is accessed again.
			nilArgs[key] = true
		end
		fetchedArgs[key] = val
	end

	function mt.__pairs()
		if not donePairs then
			donePairs = true
			mergeArgs( argTables )
		end
		return pairs( fetchedArgs )
	end

	local function inext(t, i)
		-- This uses our __index metamethod
		local v = t[i + 1]
		if v ~= nil then
			return i + 1, v
		end
	end

	function mt.__ipairs(t)
		return inext, t, 0
	end

	return setmetatable( {}, mt )
end