Module:Sandbox/Yestarhumeler/Embedded

From Fallen London Wiki (Staging)

Documentation for this module may be created at Module:Sandbox/Yestarhumeler/Embedded/doc

local p = {}

local EMBEDDED_OPTIONS_FORMAT = '|%sEmbeddedOption|heading=%s|caption=%s|SmallHeading=%s%s|'

--Pre-declare options so it can be called from find_options
local options

local function get_arg_or_nil(args, name)
	local arg = args[name]
	if arg == '' then
		return nil
	end
	return arg
end

local function get_arg(args, parent_args, name, default)
	return get_arg_or_nil(args, name) or get_arg_or_nil(parent_args, name) or default
end

local function get_title_from_heading(heading, default)
	if heading then
		return heading:gsub('|.+', '', 1)
	end
	return default
end

local function get_page_contents(frame, title)
	local title_obj = mw.title.new(title, '')
	local full_title = title_obj.nsText .. ':' .. title_obj.text
	return frame:expandTemplate{title = full_title, args = {}}
end

--[[
Invert {{UnB}} effects.

Since info lines are bolded when unembedded but not bolded in embedcontext,
keeping {{UnB}} unaltered creates unbalanced tags that leak past the info line.
Inverting them solves this while maintaining the intended emphasis effect.

We do this here instead of by testing embedcontext in {{UnB}} because {{UnB}}s
used outside of info lines should not be altered.

Input parameters:
    @str: a string with the info line

Returns a string with the detected {{UnB}}s inverted.
]]
local function invert_unb(str)
	--[[
	    NB: This approach sacrifices edge case support for simplicity. In particular:
	    * Weird stuff like "{{UnB|{{UnB|text}}}}"" will convert even more weirdly
	    * The first <b> that doesn't correspond to an earlier </b> will stop
	      further conversion, so something like "{{UnB|1}} <b>2</b> {{UnB|3}}"
	      will become "<b>1</b> <b>2</b> {{UnB|3}}" whereas "<b>X</b> {{UnB|Y}}"
	      will be completely unaltered.
	    Neither case is expected to be necessary, but a more complex approach can
	    be considered if that expectation ever proves incorrect.
	]]
	if (str:find('</b>') or str:len()) < (str:find('<b>') or 0) then
        return str
            :gsub('</b>(.-)<b>', '<b>%1</b>')
    end
    return str
end

local function get_info_line(contents, heading)
	local info_line = contents:match('<b><span>(' .. heading .. ' .-)</span></b>')
	if not info_line then
		return ''
	end
	return invert_unb(info_line)
	         :gsub('\n%*', ', ')  -- condense lists to one line
	         :gsub('%[%[Category:.-%]%]', '')  -- remove categories
	         .. '<br/>'  -- break between lines
end

local function transform_action_contents(contents)
	-- Scale down small images from 40px to 20px
	local pc = contents:gsub('small%.png', 'small.png|20px')
	-- Remove paragraphs
	pc = pc:gsub('^.-(==+ ?)', '\n&nbsp;\n%1')
	-- Remove Success/Failure Instructions
	pc = pc:gsub('...<i>[SF]?[au]?.- Instructions.-</i>\'+', '')
	-- Remove categories
	pc = pc:gsub('%[%[Category:.-%]%]', '')
    -- Remove SMW sets
	pc = pc:gsub('%[%[.-::.-%]%]', '')
	pc = pc:gsub('%{%{#set:.-%}%}', '')
    -- Trim down carriage returns
    pc = pc:gsub('\n\n', '\n')
    -- Reformat headings
    pc = pc:gsub('==(.+)==', '<div class="pseudo-h2">%1</div>')
    -- Trim trailing whitespace
    return pc:gsub('\n+%s*$', '')
end

--[[
Turn Options into a "pseudo-template" format for use in Embed.

This is a sort of hack to prevent needing to fully re-render Options multiple times.

Input parameters:
    @heading: A string with the options heading, potentially including an alternate appearance
    @caption: A string with the non-linkified caption
    @use_small_heading: A boolean, true if the heading should be rendered small

Returns a string with the formatted pseudo-template.
]]
local function embedded_options(heading, caption, use_small_heading)
	local small_heading = ''
	if use_small_heading then
		small_heading = 'yes'
	end
	return string.format(
    	EMBEDDED_OPTIONS_FORMAT, '%%', heading, caption, small_heading, '%%')
end

--[[
Render an {{Options}}

Input parameters:
    @frame: frame object
    @heading: A string with the options heading, potentially including an alternate appearance

A string with the fully rendered {{Options}}
]]
options = function(frame, heading)
	local title = get_title_from_heading(heading, 'Scheme: Set up a Salon')
	local success, contents = pcall(get_page_contents, frame, title)
	if not success then
	    return '404'
	end

	local action_cost = get_info_line(contents, 'Action Cost:')
	local unlocked_with = get_info_line(contents, 'Unlocked with')
	local locked_with = get_info_line(contents, 'Locked with')
	local friend_unlocked_with = get_info_line(contents, 'Your friend needs')
	local transformed = transform_action_contents(contents)
    
    local inner = action_cost .. unlocked_with .. locked_with ..
            friend_unlocked_with .. transformed
    return inner
end

function p.options(frame)
	local args = frame.args
	local parent_args = frame:getParent().args
	local heading = get_arg(args, parent_args, 1, nil)
	return options(frame, heading)
end

return p