Module:Buffer Explained
--local function Valid(v)--type validation if v and v~=true then--reject nil/boolean; faster than 2 type comparisons local str = tostring(v)--functions not filtered since unlikely passed by accident (Scribunto does not have userdata/thread types) if str~=v and str
'table' then return rawget(v, 1) and table.concat(v) end--tostring(string-type) returns same ref; same refs compare faster than type if str~= then return str end--numbers are coerced to string per table.concat op; appending in string form saves ops on repeat concat endendlocal noOp, MBpairs = functionend do local iMap, vMap, oMap, pIter, pOther, pFast, Next--Map local function init--init = noOp after first run function Next(t) return next, t end--slightly faster to do this than to use select function pIter(t, k) k = (iMap[t] or MBpairs(t, true) and iMap[t])[not k and 1 or vMap[t][k]] return k, t[k] end--don't use rawget; accepting unmapped tables does not measurably affect performance. function pOther(t, k) k = (oMap[t] or MBpairs(t, true) and oMap[t])[nil==k and 1 or vMap[t][k]] return k, t[k] end--comparison to nil because false is a valid key function pFast(t, k) k = not k and 1 or k < (vMap[t] or #t) and k + 1 or nil return k, t[k] end--mapless iterator; almost as fast as native ipairs; slight performance penalty when length not cached --k and k < (vMap[t] or #t) and k + 1 or not k and 1 or nil return k, t[k] end--mapless iterator; almost as fast as native ipairs; slight performance penalty when length not cached local mk = --use mode 'k'; found that mode 'kv' sometimes garbage collects maps mid-loop (may not error because iterators auto re-map, but that's expensive) init, iMap, vMap, oMap = noOp, setmetatable(mk), setmetatable(mk), setmetatable(mk)--iMap is numeric keys, oMap is non-numeric keys, and vMap points to next key end function MBpairs(t, ...)--pairs always iterates in order local iter, ex = ... iter = iter
init--nil if iter and not oMap[t] and ex
nil and rawget(t, 1)~=nil and next(t, #t)
nil then--while possible to miss keys, more thorough check would negate the benefit of pFast vMap[t] = #t return pFast, t, nil elseif ... or not vMap[t] or select('#', ...)~=1 then local ti, tn, to, n =,,, #t--reduces table lookups iMap[t], vMap[t], oMap[t] = ti, tn, to for k = 1, n do ti[k], tn[k] = k, k + 1 end--stage one avoids number type checking op in stage two for most numeric keys for k in (ex or Next)(t) do if not tn[k] then table.insert(tonumber(k)~=k and to or ti, k) end end if #ti~=n then table.sort(ti) for k = 1, #ti do tn[ti[k]] = k + 1 end--somewhat wasteful, but trying to avoid overwriting can be even more expensive end for k = 1, #to do tn[to[k]] = k + 1 end end return iter and pIter or oMap[t] and pOther or noOp, t--noOp for mapless endendlocal parent, rawkey, spec do--new scope for variables not reused outside (reduces number of var names that need to checked outside of scope) local mkv = --shared meta for Buffer parent property, raw mode, and specialized functions parent, rawkey, spec = setmetatable(mkv), setmetatable(mkv), setmetatable(mkv)--shared meta less memoryend
local MB, MBi, MBmix, buffHTML, gfuncs, noCache, Element do--minimize number of locals per scope to reduce time spent sifting through irrelevant variable names local _stream do local stream--keep stream near top of scope local function init(f)--init = noOp after first run local function each(self, ...) for k = 1, select('#', ...) do k = Valid(select(k, ...))--slightly faster than table.insert(self, (Valid(select(k, ...)))) if k then table.insert(self, k) end end return self end init, stream, _stream = noOp, for k, v in next, MB do stream[k] = stream[k] or v end setmetatable(stream, getmetatable(MB)) end function _stream(self, ...) self.last_concat = init return setmetatable(self, stream):each(...) end end local function isMBfunc(Buffer, s, ...)--helper for :getParent-like methods (including getBuffer which does not return a parent) return s and (select('#', ...)
0 and--eventually should figure out to make this work for :getHTML which is very similar (not rawkey[s] and tostring(s):match'^_.*' and MB.__index(Buffer, s) and MB.__index(Buffer, s)(Buffer) or MBmix(Buffer, s))--unprefixed function names append as a string or assert(MB.__index(Buffer, s), ('" %s " does not match any available Module:Buffer function'):format(s))(Buffer, ...)--getParent is a one-way trip so one-time assert not expensive ) or Buffer end local function MBselect(n, ...)--helper for :_out and :_str local n, seps = n - 1, if type(seps[n])
'table' then if buffHTML and rawget(seps[n], buffHTML) then return ... end setmetatable(seps,)[n] = nil end return ..., seps end local _inHTML do local lastBuffer, lastHTML local function init(...)--init replaced and new version called on return local create, mwFunc = mw.html.create do local mwHTMLmeta = getmetatable(create) buffHTML, mwFunc, _inHTML = setmetatable(mw.clone(mwHTMLmeta), getmetatable(MB)), mwHTMLmeta.__index--buffHTML declared near top of module; remove _inHTML from outer scope function init(nodes, ...) local name, args, tag = select(... and type(...)
'table' and 1 or 2, nil, ...) tag = create(Valid(name), args) if nodes then table.insert(nodes, tag.parent and tag or rawset(tag, 'parent', parent[nodes])) end if args then local a, b = args.selfClosing, args.parent args.selfClosing, args.parent = nil if next(args) then Element._add(parent(tag.nodes, tag), args) end args.selfClosing, args.parent = a, b--in case args is reused end return tag end for k, v in next, do buffHTML[k] = v or MB[k] end end local nonSelf, BHi =, buffHTML.__index do local g g = setmetatable(nonSelf, g) setmetatable(BHi, g) end for k in next, nonSelf do--any HTML objects returned by these funcs will be granted Module:Buffer enhancements local func = mwFunc[k] BHi[k] = function(t, ...) local HTML = func(t, ...) return parent[HTML] and HTML or setmetatable(parent(HTML, t), buffHTML) end end do local function joinNode(HTML, sep) local nodes, join = HTML.nodes if noCache and rawkey[sep] or Valid(sep) then join, HTML.nodes = tostring(rawset(HTML, 'nodes',)), nodes end return join or tostring(HTML) end for k, v in next, do BHi[k] = v end end do local htmlArg, skip, outFuncs =, do local out local function func(nodes, ...) return out(parent[nodes], ...) end outFuncs = setmetatable end Element = local tempMeta = function tempMeta.__index(t, i) return tempMeta.copy[i] and rawset(t, i, MBi._cc(false, 0, t.orig[i]))[i] or t.orig[i] end rawkey[setmetatable(Element, {__index = outFuncs, __concat=function(Element, v) return setmetatable({nodes=spec({}, Element),orig=parent[v]}, tempMeta) end})] = math.huge end function MBi:getHTML(...) lastBuffer = self if ... then if select('#', ...)
1 then return not rawkey[s] and tostring(...):match'^_' and BHi[...] and BHi[...](lastHTML) or lastHTML(...) else return assert(BHi[...], ('" %s " does not match any mw.html or Buffer-mw.html function'):format(tostring(...)))(lastHTML, select(2, ...)) end end return lastHTML end function MBi:_html(...) return MBi._(self, lastHTML, select(spec[self]
Element and select('#', ...)
0 and 1 or 2, true, ...)) end return init(...) end function _inHTML(self, ...) local HTML = init(nil, ...) if HTML.selfClosing and spec[self]
Element then self.last_concat = table.insert(self, HTML) return self end lastBuffer, lastHTML = self, setmetatable(parent(HTML, self), buffHTML)--set after 'args' table processed by :_add return HTML end end local _var, unbuild do local prev, rebuild local function init(...)--init replaced before return local function pick(b, v) return b and table.insert(b, v) or v end local function c(a, num) return rawset(a.a or a, 0, a[0] and a[0] + a.c or num and a[1] or a[1]:byte)[0] end local same, build, alt =,, local function shift(t, c) t[0] = t[0] and t[0] + c or t:_build and t[0] - t.c + c if t.table then t[0] = (t[0] - 1) % #t[1] + 1 end end function rebuild(...) local v, c = ... if v or select('#', ...)
0 then if v and not c then return prev end local meta, c = select(v and 1 or 3, alt, c, same, 0) return setmetatable(meta) elseif v
nil then--no-op elseif c then shift(prev, c)--v
false else prev:_build end end init, noCache = function(v, c) prev = setmetatable(build) return prev end, true return init(...) end function unbuild(sep) for k, v in MBpairs(sep, nil) do k = getmetatable(v) if k and (k
build or k
alt) then shift(v.a or v, -v.c) end end end function _var(self, ...) local obj if ... and ...~=true then obj = init(...) elseif prev then if ...~=false then obj = rebuild(...) else rebuild(...) end end return obj and MBi._(self, obj, nil, true) or self end end local lib; MBi = setmetatableend
function MBmix(t, v, ...) return v and ((type(v)~='table' or getmetatable(v)) and MBi._(t, v) or (select('#', ...)
0 and spec[t] and spec[t]._add or MBi._all)(t, v, ...)) or t end--:_all always passes two args
local _G, new_G = _G--localize _G for console testing (console _G ~= module _G)return setmetatable