User:Senthe/sandbox/Module:ExperimentalEmbeddedOption

From Fallen London Wiki

local p = {}

local EMBEDDED_OPTIONS_FORMAT = '|%sEmbeddedOption|heading=%s|caption=%s|SmallHeading=%s%s|' -- Taken from FATE so we don't have to call out to the template local FONT_FATE = "FATE"

--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

--[[ Find embedded options in the "pseudo-template" format and expand into list items.

Input parameters: @frame: frame object @contents: a string with the contents of the page being embedded

Returns a string of all expanded options, wrapped in list items. ]] local function find_options(frame, contents) local search_pattern = string.format( EMBEDDED_OPTIONS_FORMAT, '%%%%', '(.-)', '(.-)', '(.-)', '%%%%') local actions = for heading, caption, small_heading in mw.ustring.gmatch(contents, search_pattern) do local use_small_heading = small_heading == 'yes'

actions = actions .. '

  • ' .. options(frame, heading, caption, use_small_heading, false) .. '
  • ' end return actions end --[==[ Find the main page image. The expected default image specification is "

    ". Any

    additional parameters specified should appear after the "|right", not before.

    Input parameters: @contents: a string with the contents of the page being embedded

    Returns a string with the image name, including the leading "File:" and trailing ".png". ]==] local function get_img(contents) img = contents:match('File:([^%]]-png)%|right[^%[]-%]%]') or 'Question.png' return 'File:' .. img end

    --[[ Find the ID, if it's recorded. Appears in a hidden div for gadget use. (See Action of Storylet templates) Some editors leave an empty filed for the parameter but this isn't always the case.

    Input parameters: @contents: a string with the contents of the page being embedded

    Returns a string with ID or an empty string if it wasn't found. ]] local function get_id(contents) return contents:match('(%d+.-)') or end

    --[[ Invert {{{1}}} effects.

    Since info lines are bolded when unembedded but not bolded in embedcontext, keeping {{{1}}} 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 {{{1}}} because {{{1}}}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 {{{1}}}s inverted. ]] local function invert_unb(str) --[[ NB: This approach sacrifices edge case support for simplicity. In particular: * Weird stuff like "text"" will convert even more weirdly * The first that doesn't correspond to an earlier will stop further conversion, so something like "1 2 3" will become "1 2 3" whereas "X 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('') or str:len()) < (str:find('') or 0) then return str :gsub('(.-)', '%1') end return str end

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

    local function transform_action_contents(frame, contents) -- Scale down small images from 40px to 20px local pc = contents:gsub('small%.png', 'small.png|20px') -- Remove paragraphs pc = pc:gsub('^.-(==+ ?)', '\n \n%1') -- Remove Success/Failure Instructions pc = pc:gsub('...[SF]?[au]?.- Instructions.-\'+', ) -- Remove categories pc = pc:gsub('%[%[Category:.-%]%]', ) -- Remove SMW sets (but preserve SMW tags) pc = pc:gsub('%[%[.-[^S][^M][^W]::.-%]%]', ) pc = pc:gsub('%{%{#set:.-%}%}', ) -- Trim down carriage returns pc = pc:gsub('\n\n', '\n') -- Trim trailing whitespace pc = pc:gsub('\n+%s*$', ) -- Change table pointer to page pc = pc:gsub('%[See below%]', '%[See page%]')

    -- Replace headers (

    -
    ) with fake headers (<xh2>-<xh6>) for i = 6, 2, -1 do -- HTML headers local tag = 'h' .. i pc = pc:gsub('<%s*' .. tag .. '%s*>', '<x' .. tag .. '>') pc = pc:gsub('</%s*' .. tag .. '%s*>', '</x' .. tag .. '>') -- Wikitext headers local pat = for j = 1, i do pat = pat .. '=' end pc = pc:gsub( pat .. '%s*([^\n]-)%s*' .. pat, '<x' .. tag .. '>%1</x' .. tag .. '>' ) -- Now fake headers must be expanded as tag extensions tag = 'x' .. tag while pc:find('<' .. tag .. '>.-</' .. tag .. '>') do local _, header _, _, header = pc:find('<' .. tag .. '>(.-)</' .. tag .. '>') local header_clean = mw.text.trim( mw.text.killMarkers(header) ) -- `header` will be used in a pattern, escaping non-word characters header = header:gsub('(%W)', '%%%1') pc = pc:gsub( '<' .. tag .. '>' .. header .. '</' .. tag .. '>', frame:extensionTag(tag, header_clean) ) end end return pc end local function format_heading(heading, caption, use_small_heading) local out = if use_small_heading then out = out .. '' .. heading .. '' else out = out .. '' .. heading .. '' end if caption and caption ~= then out = out .. ' ' .. caption .. '' end return out end local function make_option(heading, caption, use_small_heading, title, img, contents, id) return '
    ' ..

    '' .. format_heading(heading, caption, use_small_heading) ..

    '
    • ' ..

      ' Spoiler ' ..

      '
      ' .. contents .. '\n^
    '

    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
    Salon3.png
    [[]]

    Input parameters: @frame: frame object @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 @only_contents: A boolean, true if the HTML of make_options() is undesired

    A string with the fully rendered
    Salon3.png
    [[]]

    ]] options = function(frame, heading, caption, use_small_heading, only_contents) local title = get_title_from_heading(heading, 'Scheme: Set up a Salon') heading = heading or caption = caption or if caption == then -- Automatically format Fate costs when possible local name, cost = mw.ustring.match(title, '^(.-) %((%d+) FATE%)$') if name then caption = '(' .. cost .. ' ' .. FONT_FATE .. ')' if heading == title then heading = title .. '|' .. name end end end local success, contents = pcall(get_page_contents, frame, title) if not success then return make_option(heading, caption, use_small_heading, title, 'File:Question.png', , 'NaN') end

    local id = get_id(contents) local img = get_img(contents) 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(frame, contents)

    if id == then id = 'MISSING' end local inner = 'right' inner = inner .. action_cost .. unlocked_with .. locked_with .. friend_unlocked_with .. transformed if only_contents then return inner else return make_option(heading, caption, use_small_heading, title, img, inner, id) end end

    --[[

    Render an
    Ropecourt.png
    [[]]


    Input parameters: @frame: frame object @heading: A string with the embed 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

    A string with the fully rendered
    Ropecourt.png
    [[]]

    ]] local function embed(frame, heading, caption, use_small_heading) local title = get_title_from_heading(heading, 'A fine day in the Flit') heading = heading or local success, contents = pcall(get_page_contents, frame, title) if not success then return make_option(heading, caption, use_small_heading, title, 'File:Question.png', , 'NaN') end

    local id = get_id(contents) local img = get_img(contents)

    if id == then id = 'MISSING' end

    local success, options_list = pcall(find_options, frame, contents) if not success then return make_option(heading, caption, use_small_heading, title, img, contents, id) end

    local appears_in = get_info_line(contents, 'Storylet appears in') local drawn_in = get_info_line(contents, 'Card drawn in') local unlocked_with = get_info_line(contents, 'Unlocked with') local locked_with = get_info_line(contents, 'Locked with') local frequency = get_info_line(contents, 'Occurs with')

    --local inner = 'right' local inner = appears_in .. drawn_in .. unlocked_with .. locked_with .. frequency

    inner = inner .. '

    Options:

    ' inner = inner .. '
      ' .. find_options(frame, contents) .. '
    '

    return make_option(heading, caption, use_small_heading, title, img, inner, id) end

    function p.options(frame) local args = frame.args local parent_args = frame:getParent().args local heading = get_arg(args, parent_args, 1, nil) local caption = get_arg(args, parent_args, 2, nil) local use_small_heading = get_arg(args, parent_args, 'SmallHeading', false) local only_contents = get_arg(args, parent_args, 'OnlyContents', false) local embedded = frame:callParserFunction('#var', 'embeddedoptions') ~= if embedded then return embedded_options(heading or , caption or , use_small_heading) end return options(frame, heading, caption, use_small_heading, only_contents) end

    function p.embed(frame) local args = frame.args local parent_args = frame:getParent().args local heading = get_arg(args, parent_args, 1, nil) local caption = get_arg(args, parent_args, 2, nil) local use_small_heading = get_arg(args, parent_args, 'SmallHeading', false) return embed(frame, heading, caption, use_small_heading) end

    return p