<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://battery.knowledge-graph.eu/w/index.php?action=history&amp;feed=atom&amp;title=Module%3ALustache%2FRenderer</id>
	<title>Module:Lustache/Renderer - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://battery.knowledge-graph.eu/w/index.php?action=history&amp;feed=atom&amp;title=Module%3ALustache%2FRenderer"/>
	<link rel="alternate" type="text/html" href="https://battery.knowledge-graph.eu/w/index.php?title=Module:Lustache/Renderer&amp;action=history"/>
	<updated>2026-04-29T15:16:21Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.39.5</generator>
	<entry>
		<id>https://battery.knowledge-graph.eu/w/index.php?title=Module:Lustache/Renderer&amp;diff=46&amp;oldid=prev</id>
		<title>Maintenance script: Install package: OSW Core</title>
		<link rel="alternate" type="text/html" href="https://battery.knowledge-graph.eu/w/index.php?title=Module:Lustache/Renderer&amp;diff=46&amp;oldid=prev"/>
		<updated>2023-12-15T05:13:33Z</updated>

		<summary type="html">&lt;p&gt;Install package: OSW Core&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;local Scanner  = require(&amp;quot;Module:Lustache/Scanner&amp;quot;)&lt;br /&gt;
local Context  = require(&amp;quot;Module:Lustache/Context&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
local error, ipairs, pairs, setmetatable, tostring, type = &lt;br /&gt;
      error, ipairs, pairs, setmetatable, tostring, type &lt;br /&gt;
local math_floor, math_max, string_find, string_gsub, string_split, string_sub, table_concat, table_insert, table_remove =&lt;br /&gt;
      math.floor, math.max, string.find, string.gsub, string.split, string.sub, table.concat, table.insert, table.remove&lt;br /&gt;
&lt;br /&gt;
local patterns = {&lt;br /&gt;
  white = &amp;quot;%s*&amp;quot;,&lt;br /&gt;
  space = &amp;quot;%s+&amp;quot;,&lt;br /&gt;
  nonSpace = &amp;quot;%S&amp;quot;,&lt;br /&gt;
  eq = &amp;quot;%s*=&amp;quot;,&lt;br /&gt;
  curly = &amp;quot;%s*}&amp;quot;,&lt;br /&gt;
  tag = &amp;quot;[#\\^/&amp;gt;{&amp;amp;=!?]&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local html_escape_characters = {&lt;br /&gt;
  [&amp;quot;&amp;amp;&amp;quot;] = &amp;quot;&amp;amp;amp;&amp;quot;,&lt;br /&gt;
  [&amp;quot;&amp;lt;&amp;quot;] = &amp;quot;&amp;amp;lt;&amp;quot;,&lt;br /&gt;
  [&amp;quot;&amp;gt;&amp;quot;] = &amp;quot;&amp;amp;gt;&amp;quot;,&lt;br /&gt;
  [&amp;#039;&amp;quot;&amp;#039;] = &amp;quot;&amp;amp;quot;&amp;quot;,&lt;br /&gt;
  [&amp;quot;&amp;#039;&amp;quot;] = &amp;quot;&amp;amp;#39;&amp;quot;,&lt;br /&gt;
  [&amp;quot;/&amp;quot;] = &amp;quot;&amp;amp;#x2F;&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local block_tags = {&lt;br /&gt;
  [&amp;quot;#&amp;quot;] = true,&lt;br /&gt;
  [&amp;quot;^&amp;quot;] = true,&lt;br /&gt;
  [&amp;quot;?&amp;quot;] = true,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local function is_array(array)&lt;br /&gt;
  if type(array) ~= &amp;quot;table&amp;quot; then return false end&lt;br /&gt;
  local max, n = 0, 0&lt;br /&gt;
  for k, _ in pairs(array) do&lt;br /&gt;
    if not (type(k) == &amp;quot;number&amp;quot; and k &amp;gt; 0 and math_floor(k) == k) then&lt;br /&gt;
      return false &lt;br /&gt;
    end&lt;br /&gt;
    max = math_max(max, k)&lt;br /&gt;
    n = n + 1&lt;br /&gt;
  end&lt;br /&gt;
  return n == max&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Low-level function that compiles the given `tokens` into a&lt;br /&gt;
-- function that accepts two arguments: a Context and a&lt;br /&gt;
-- Renderer.&lt;br /&gt;
&lt;br /&gt;
local function compile_tokens(tokens, originalTemplate)&lt;br /&gt;
  local subs = {}&lt;br /&gt;
&lt;br /&gt;
  local function subrender(i, tokens)&lt;br /&gt;
    if not subs[i] then&lt;br /&gt;
      local fn = compile_tokens(tokens, originalTemplate)&lt;br /&gt;
      subs[i] = function(ctx, rnd) return fn(ctx, rnd) end&lt;br /&gt;
    end&lt;br /&gt;
    return subs[i]&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  local function render(ctx, rnd)&lt;br /&gt;
    local buf = {}&lt;br /&gt;
    local token, section&lt;br /&gt;
    for i, token in ipairs(tokens) do&lt;br /&gt;
      local t = token.type&lt;br /&gt;
      buf[#buf+1] = &lt;br /&gt;
        t == &amp;quot;?&amp;quot; and rnd:_conditional(&lt;br /&gt;
          token, ctx, subrender(i, token.tokens)&lt;br /&gt;
        ) or&lt;br /&gt;
        t == &amp;quot;#&amp;quot; and rnd:_section(&lt;br /&gt;
          token, ctx, subrender(i, token.tokens), originalTemplate&lt;br /&gt;
        ) or&lt;br /&gt;
        t == &amp;quot;^&amp;quot; and rnd:_inverted(&lt;br /&gt;
          token.value, ctx, subrender(i, token.tokens)&lt;br /&gt;
        ) or&lt;br /&gt;
        t == &amp;quot;&amp;gt;&amp;quot; and rnd:_partial(token.value, ctx, originalTemplate) or&lt;br /&gt;
        (t == &amp;quot;{&amp;quot; or t == &amp;quot;&amp;amp;&amp;quot;) and rnd:_name(token.value, ctx, false) or&lt;br /&gt;
        t == &amp;quot;name&amp;quot; and rnd:_name(token.value, ctx, true) or&lt;br /&gt;
        t == &amp;quot;text&amp;quot; and token.value or &amp;quot;&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    return table_concat(buf)&lt;br /&gt;
  end&lt;br /&gt;
  return render&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function escape_tags(tags)&lt;br /&gt;
&lt;br /&gt;
  return {&lt;br /&gt;
    string_gsub(tags[1], &amp;quot;%%&amp;quot;, &amp;quot;%%%%&amp;quot;)..&amp;quot;%s*&amp;quot;,&lt;br /&gt;
    &amp;quot;%s*&amp;quot;..string_gsub(tags[2], &amp;quot;%%&amp;quot;, &amp;quot;%%%%&amp;quot;),&lt;br /&gt;
  }&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function nest_tokens(tokens)&lt;br /&gt;
  local tree = {}&lt;br /&gt;
  local collector = tree &lt;br /&gt;
  local sections = {}&lt;br /&gt;
  local token, section&lt;br /&gt;
&lt;br /&gt;
  for i,token in ipairs(tokens) do&lt;br /&gt;
    if block_tags[token.type] then&lt;br /&gt;
      token.tokens = {}&lt;br /&gt;
      sections[#sections+1] = token&lt;br /&gt;
      collector[#collector+1] = token&lt;br /&gt;
      collector = token.tokens&lt;br /&gt;
    elseif token.type == &amp;quot;/&amp;quot; then&lt;br /&gt;
      if #sections == 0 then&lt;br /&gt;
        error(&amp;quot;Unopened section: &amp;quot;..token.value)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      -- Make sure there are no open sections when we&amp;#039;re done&lt;br /&gt;
      section = table_remove(sections, #sections)&lt;br /&gt;
&lt;br /&gt;
      if not section.value == token.value then&lt;br /&gt;
        error(&amp;quot;Unclosed section: &amp;quot;..section.value)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      section.closingTagIndex = token.startIndex&lt;br /&gt;
&lt;br /&gt;
      if #sections &amp;gt; 0 then&lt;br /&gt;
        collector = sections[#sections].tokens&lt;br /&gt;
      else&lt;br /&gt;
        collector = tree&lt;br /&gt;
      end&lt;br /&gt;
    else&lt;br /&gt;
      collector[#collector+1] = token&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  section = table_remove(sections, #sections)&lt;br /&gt;
&lt;br /&gt;
  if section then&lt;br /&gt;
    error(&amp;quot;Unclosed section: &amp;quot;..section.value)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  return tree&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Combines the values of consecutive text tokens in the given `tokens` array&lt;br /&gt;
-- to a single token.&lt;br /&gt;
local function squash_tokens(tokens)&lt;br /&gt;
  local out, txt = {}, {}&lt;br /&gt;
  local txtStartIndex, txtEndIndex&lt;br /&gt;
  for _, v in ipairs(tokens) do&lt;br /&gt;
    if v.type == &amp;quot;text&amp;quot; then&lt;br /&gt;
      if #txt == 0 then&lt;br /&gt;
        txtStartIndex = v.startIndex&lt;br /&gt;
      end&lt;br /&gt;
      txt[#txt+1] = v.value&lt;br /&gt;
      txtEndIndex = v.endIndex&lt;br /&gt;
    else&lt;br /&gt;
      if #txt &amp;gt; 0 then&lt;br /&gt;
        out[#out+1] = { type = &amp;quot;text&amp;quot;, value = table_concat(txt), startIndex = txtStartIndex, endIndex = txtEndIndex }&lt;br /&gt;
        txt = {}&lt;br /&gt;
      end&lt;br /&gt;
      out[#out+1] = v&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
  if #txt &amp;gt; 0 then&lt;br /&gt;
    out[#out+1] = { type = &amp;quot;text&amp;quot;, value = table_concat(txt), startIndex = txtStartIndex, endIndex = txtEndIndex  }&lt;br /&gt;
  end&lt;br /&gt;
  return out&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function make_context(view)&lt;br /&gt;
  if not view then return view end&lt;br /&gt;
  return getmetatable(view) == Context and view or Context:new(view)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local renderer = { }&lt;br /&gt;
&lt;br /&gt;
function renderer:clear_cache()&lt;br /&gt;
  self.cache = {}&lt;br /&gt;
  self.partial_cache = {}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function renderer:compile(tokens, tags, originalTemplate)&lt;br /&gt;
  tags = tags or self.tags&lt;br /&gt;
  if type(tokens) == &amp;quot;string&amp;quot; then&lt;br /&gt;
    tokens = self:parse(tokens, tags)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  local fn = compile_tokens(tokens, originalTemplate)&lt;br /&gt;
&lt;br /&gt;
  return function(view)&lt;br /&gt;
    return fn(make_context(view), self)&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function renderer:render(template, view, partials)&lt;br /&gt;
  if type(self) == &amp;quot;string&amp;quot; then&lt;br /&gt;
    error(&amp;quot;Call mustache:render, not mustache.render!&amp;quot;)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  if partials then&lt;br /&gt;
    -- remember partial table&lt;br /&gt;
    -- used for runtime lookup &amp;amp; compile later on&lt;br /&gt;
    self.partials = partials&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  if not template then&lt;br /&gt;
    return &amp;quot;&amp;quot;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  local fn = self.cache[template]&lt;br /&gt;
&lt;br /&gt;
  if not fn then&lt;br /&gt;
    fn = self:compile(template, self.tags, template)&lt;br /&gt;
    self.cache[template] = fn&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  return fn(view)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function renderer:_conditional(token, context, callback)&lt;br /&gt;
  local value = context:lookup(token.value)&lt;br /&gt;
&lt;br /&gt;
  if value then&lt;br /&gt;
    return callback(context, self)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  return &amp;quot;&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function renderer:_section(token, context, callback, originalTemplate)&lt;br /&gt;
  local value = context:lookup(token.value)&lt;br /&gt;
&lt;br /&gt;
  if type(value) == &amp;quot;table&amp;quot; then&lt;br /&gt;
    if is_array(value) then&lt;br /&gt;
      local buffer = &amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
      for i,v in ipairs(value) do&lt;br /&gt;
        buffer = buffer .. callback(context:push(v), self)&lt;br /&gt;
      end&lt;br /&gt;
&lt;br /&gt;
      return buffer&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return callback(context:push(value), self)&lt;br /&gt;
  elseif type(value) == &amp;quot;function&amp;quot; then&lt;br /&gt;
    local section_text = string_sub(originalTemplate, token.endIndex+1, token.closingTagIndex - 1)&lt;br /&gt;
&lt;br /&gt;
    local scoped_render = function(template)&lt;br /&gt;
      return self:render(template, context)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return value(section_text, scoped_render) or &amp;quot;&amp;quot;&lt;br /&gt;
  else&lt;br /&gt;
    if value then&lt;br /&gt;
      return callback(context, self)&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  return &amp;quot;&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function renderer:_inverted(name, context, callback)&lt;br /&gt;
  local value = context:lookup(name)&lt;br /&gt;
&lt;br /&gt;
  -- From the spec: inverted sections may render text once based on the&lt;br /&gt;
  -- inverse value of the key. That is, they will be rendered if the key&lt;br /&gt;
  -- doesn&amp;#039;t exist, is false, or is an empty list.&lt;br /&gt;
&lt;br /&gt;
  if value == nil or value == false or (type(value) == &amp;quot;table&amp;quot; and is_array(value) and #value == 0) then&lt;br /&gt;
    return callback(context, self)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  return &amp;quot;&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function renderer:_partial(name, context, originalTemplate)&lt;br /&gt;
  local fn = self.partial_cache[name]&lt;br /&gt;
&lt;br /&gt;
  -- check if partial cache exists&lt;br /&gt;
  if (not fn and self.partials) then&lt;br /&gt;
&lt;br /&gt;
    local partial = self.partials[name]&lt;br /&gt;
    if (not partial) then&lt;br /&gt;
      return &amp;quot;&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
    &lt;br /&gt;
    -- compile partial and store result in cache&lt;br /&gt;
    fn = self:compile(partial, nil, partial)&lt;br /&gt;
    self.partial_cache[name] = fn&lt;br /&gt;
  end&lt;br /&gt;
  return fn and fn(context, self) or &amp;quot;&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function renderer:_name(name, context, escape)&lt;br /&gt;
  local value = context:lookup(name)&lt;br /&gt;
&lt;br /&gt;
  if type(value) == &amp;quot;function&amp;quot; then&lt;br /&gt;
    value = value(context.view)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  local str = value == nil and &amp;quot;&amp;quot; or value&lt;br /&gt;
  str = tostring(str)&lt;br /&gt;
&lt;br /&gt;
  if escape then&lt;br /&gt;
    return string_gsub(str, &amp;#039;[&amp;amp;&amp;lt;&amp;gt;&amp;quot;\&amp;#039;/]&amp;#039;, function(s) return html_escape_characters[s] end)&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  return str&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Breaks up the given `template` string into a tree of token objects. If&lt;br /&gt;
-- `tags` is given here it must be an array with two string values: the&lt;br /&gt;
-- opening and closing tags used in the template (e.g. [&amp;quot;&amp;lt;%&amp;quot;, &amp;quot;%&amp;gt;&amp;quot;]). Of&lt;br /&gt;
-- course, the default is to use mustaches (i.e. Mustache.tags).&lt;br /&gt;
function renderer:parse(template, tags)&lt;br /&gt;
  tags = tags or self.tags&lt;br /&gt;
  local tag_patterns = escape_tags(tags)&lt;br /&gt;
  local scanner = Scanner:new(template)&lt;br /&gt;
  local tokens = {} -- token buffer&lt;br /&gt;
  local spaces = {} -- indices of whitespace tokens on the current line&lt;br /&gt;
  local has_tag = false -- is there a {{tag} on the current line?&lt;br /&gt;
  local non_space = false -- is there a non-space char on the current line?&lt;br /&gt;
&lt;br /&gt;
  -- Strips all whitespace tokens array for the current line if there was&lt;br /&gt;
  -- a {{#tag}} on it and otherwise only space&lt;br /&gt;
  local function strip_space()&lt;br /&gt;
    if has_tag and not non_space then&lt;br /&gt;
      while #spaces &amp;gt; 0 do&lt;br /&gt;
        table_remove(tokens, table_remove(spaces))&lt;br /&gt;
      end&lt;br /&gt;
    else&lt;br /&gt;
      spaces = {}&lt;br /&gt;
    end&lt;br /&gt;
    has_tag = false&lt;br /&gt;
    non_space = false&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  local type, value, chr&lt;br /&gt;
&lt;br /&gt;
  while not scanner:eos() do&lt;br /&gt;
    local start = scanner.pos&lt;br /&gt;
&lt;br /&gt;
    value = scanner:scan_until(tag_patterns[1])&lt;br /&gt;
&lt;br /&gt;
    if value then&lt;br /&gt;
      for i = 1, #value do&lt;br /&gt;
        chr = string_sub(value,i,i)&lt;br /&gt;
&lt;br /&gt;
        if string_find(chr, &amp;quot;%s+&amp;quot;) then&lt;br /&gt;
          spaces[#spaces+1] = #tokens + 1&lt;br /&gt;
        else&lt;br /&gt;
          non_space = true&lt;br /&gt;
        end&lt;br /&gt;
&lt;br /&gt;
        tokens[#tokens+1] = { type = &amp;quot;text&amp;quot;, value = chr, startIndex = start, endIndex = start }&lt;br /&gt;
        start = start + 1&lt;br /&gt;
        if chr == &amp;quot;\n&amp;quot; then&lt;br /&gt;
          strip_space()&lt;br /&gt;
        end&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if not scanner:scan(tag_patterns[1]) then&lt;br /&gt;
      break&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    has_tag = true&lt;br /&gt;
    type = scanner:scan(patterns.tag) or &amp;quot;name&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    scanner:scan(patterns.white)&lt;br /&gt;
&lt;br /&gt;
    if type == &amp;quot;=&amp;quot; then&lt;br /&gt;
      value = scanner:scan_until(patterns.eq)&lt;br /&gt;
      scanner:scan(patterns.eq)&lt;br /&gt;
      scanner:scan_until(tag_patterns[2])&lt;br /&gt;
    elseif type == &amp;quot;{&amp;quot; then&lt;br /&gt;
      local close_pattern = &amp;quot;%s*}&amp;quot;..tags[2]&lt;br /&gt;
      value = scanner:scan_until(close_pattern)&lt;br /&gt;
      scanner:scan(patterns.curly)&lt;br /&gt;
      scanner:scan_until(tag_patterns[2])&lt;br /&gt;
    else&lt;br /&gt;
      value = scanner:scan_until(tag_patterns[2])&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if not scanner:scan(tag_patterns[2]) then&lt;br /&gt;
      error(&amp;quot;Unclosed tag &amp;quot; .. value .. &amp;quot; of type &amp;quot; .. type .. &amp;quot; at position &amp;quot; .. scanner.pos)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    tokens[#tokens+1] = { type = type, value = value, startIndex = start, endIndex = scanner.pos - 1 }&lt;br /&gt;
    if type == &amp;quot;name&amp;quot; or type == &amp;quot;{&amp;quot; or type == &amp;quot;&amp;amp;&amp;quot; then&lt;br /&gt;
      non_space = true --&amp;gt; what does this do?&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if type == &amp;quot;=&amp;quot; then&lt;br /&gt;
      tags = string_split(value, patterns.space)&lt;br /&gt;
      tag_patterns = escape_tags(tags)&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  return nest_tokens(squash_tokens(tokens))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function renderer:new()&lt;br /&gt;
  local out = { &lt;br /&gt;
    cache         = {},&lt;br /&gt;
    partial_cache = {},&lt;br /&gt;
    tags          = {&amp;quot;{{&amp;quot;, &amp;quot;}}&amp;quot;}&lt;br /&gt;
  }&lt;br /&gt;
  return setmetatable(out, { __index = self })&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return renderer&lt;/div&gt;</summary>
		<author><name>Maintenance script</name></author>
	</entry>
</feed>