Module:Buffer: Difference between revisions

Jump to navigation Jump to search
Content added Content deleted
en>Codehydro
(slight performance improvement)
en>Codehydro
m (slight performance improvement)
Line 1: Line 1:
--[[=============================
--[[=============================
This Module was written by English wikipedia User:Codehydro
This Module was written by English wikipedia User:Codehydro
All methods were developed independently and any resemblence to other string buffer libraries would be coincidental.
All methods were developed independently and any resemblance to other string buffer libraries would be coincidental.


Licensed under Creative Commons Attribution-ShareAlike 3.0 Unported License
Licensed under Creative Commons Attribution-ShareAlike 3.0 Unported License
Line 108: Line 108:
_inHTML = function(self, ...)
_inHTML = function(self, ...)
local HTML, meta = mw.html.create(...), getmetatable(self)
local HTML, meta = mw.html.create(...), getmetatable(self)
if not buffHTMLmeta then
if not rawget(_G, 'buffHTMLmeta') then
local mwHTMLmeta = getmetatable(mw.html.create())
local mwHTMLmeta = getmetatable(mw.html.create())
buffHTMLmeta, mwHTMLfunc = rawset(mw.clone(mwHTMLmeta), mw.html, mwHTMLmeta), mwHTMLmeta.__index
buffHTMLmeta, mwHTMLfunc = rawset(mw.clone(mwHTMLmeta), mw.html, mwHTMLmeta), mwHTMLmeta.__index
Line 147: Line 147:
if k=='node' or type(v)~='table' then mwHTMLfunc[k](HTML, v)
if k=='node' or type(v)~='table' then mwHTMLfunc[k](HTML, v)
else
else
for x, y in next, v do
for _, y in mbpairs(v) do
if y and y~=true then mwHTMLfunc[k](HTML, y) end
end
for x, y in mbpairs(v, false) do
if y and y~=true then mwHTMLfunc[k](HTML, x, y) end
if y and y~=true then mwHTMLfunc[k](HTML, x, y) end
end
end
Line 170: Line 173:
buffHTMLmeta.__index[k] = function(t, ...)
buffHTMLmeta.__index[k] = function(t, ...)
local HTML = func(t, ...)
local HTML = func(t, ...)
if not HTML[next] then makeNodesBuffer(setmetatable(rawset(HTML, next, t), buffHTMLmeta)) end
if not (HTML.selfClosing or HTML[next]) then makeNodesBuffer(setmetatable(rawset(HTML, next, t), buffHTMLmeta)) end
return HTML
return HTML
end
end
Line 221: Line 224:
end,
end,
__pairs = function(t, ...)--pairs always iterates in order
__pairs = function(t, ...)--pairs always iterates in order
if not map then
if not map_i then
map = setmetatable({}, {__mode='k'})
local mk = {__mode='k'}
map_i, map_n, map_o = setmetatable({}, mk), setmetatable({}, mk), setmetatable({}, mk)--reduces table lookups
function mBuffIter(t, i) i = map[t].i[nil==i and 1 or map[t].n[i]] return i, t[i] end
function mBuffOther(t, i) i = map[t].o[nil==i and 1 or map[t].o[i]] return i, t[i] end
function mBuffIter(t, i) i = map_i[t][nil==i and 1 or map_n[t][i]] return i, t[i] end
function mBuffOther(t, i) i = map_o[t][nil==i and 1 or map_o[t][i]] return i, t[i] end
function mBuffMap(t)
function mBuffMap(t)
local ti, tn, to, n = {}, {}, {}, #t--reduces table lookups
local ti, tn, to, n = {}, {}, {}, #t--reduces table lookups
map[t] = {i = ti, n = tn, o = to}
map_i[t], map_n[t], map_o[t] = ti, tn, to
for k = 1, n do
for k = 1, n do
table.insert(ti, k)
table.insert(ti, k)
Line 242: Line 246:
end
end
end
end
end
if #ti~=n then
if #ti~=n then
table.sort(ti)
for k, v in next, ti do tn[v] = k + 1 end
table.sort(ti)
end
for k, v in next, ti do tn[v] = k + 1 end
end
end
end
end
end
end
local n = select('#', ...)
if select('#', ...)==0 or ...==true then mBuffMap(t) end
if ...==true or n==0 then mBuffMap(t) end
return ...==nil and mBuffIter or mBuffOther, t
return ...==nil and mBuffIter or mBuffOther, t
end,
end,
__ipairs = pairs
__ipairs = pairs
},{__tostring = function()return'Module:Buffer'end,
},{__tostring = function()return''end,
__call = function(self, ...)
__call = function(self, ...)
if not rawget(_G, 'libFunc') then--adds string and mw libraries; also add global functions if passed; rawget avoids error when reading nil global
if not rawget(_G, 'libFunc') then--adds string and mw libraries; also add global functions if passed; rawget avoids error when reading nil global

Revision as of 19:09, 18 February 2015

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

--[[=============================
This Module was written by English wikipedia User:Codehydro
All methods were developed independently and any resemblance to other string buffer libraries would be coincidental.

Licensed under Creative Commons Attribution-ShareAlike 3.0 Unported License
https://en.wikipedia.org/wiki/Wikipedia:Text_of_Creative_Commons_Attribution-ShareAlike_3.0_Unported_License

https://en.wikipedia.org/wiki/Module:Buffer
https://en.wikipedia.org/wiki/User:Codehydro
=============================--]]
return setmetatable({[type] = function(v)
		if v and v~=true then--reject nil/boolean; faster than 2 type() comparisons
			local str = tostring(v)
			if str~=v and str=='table' then return table.concat(v) end
			return str~='' and str or nil
		end
	end,
	[select] = function(n, ...)
		local seps, meta = {select(2, ...)}
		if type(seps[#seps])=='table' then
			meta = rawset(table.remove(seps), '__index', function(t, i) return meta[1] end)
			setmetatable(seps, meta)
		end
		return ..., seps
	end,
	__index = {
		_ = function(self, v, ...)
			local at, raw
			if select('#', ...)==1 and ...==not not... then raw = ...
			else at, raw = ... end
			if raw then self[rawset] = math.huge else v = getmetatable(self)[type](v) end
			if v or raw then
				if at then
					if tostring(at)==at then at = #self + at end
					if at < 1 then self[rawset] = math.huge
					elseif at > #self + 1 then self[rawset] = self[rawset] and math.max(at, self[rawset]) or at end
				end
				self.last_concat = table.insert(self, select(at and 1 or 2, at, v))
			end
			return self
		end,
		_nil = function(self, at, v2)
			if v2~=true and v2~=false then--faster than type(v2) ~= 'boolean'
				self.last_concat = nil
				if at=='0' or at==nil then
					self[#self] = v2
					if v2 then self[rawset] = math.huge end
				else
					if tostring(at)==at then at = #self + at end
					if v2 or self[rawset] or at < 1 or at > #self + 1 then self[at], self[rawset] = v2, math.huge
					else table.remove(self, at) end
				end
			end
			return self
		end,
		_all = function(self, t, valKey)
			for k, v in getmetatable(self).__pairs(t) do self:_mix(v) end
			if valKey then
				for k, v in getmetatable(self).__pairs(t, false) do
					if tonumber(v) then self:_(k, v) end
				end
			end
			return self
		end,
		_mix = function(self, v, a) return v and ((type(v)~='table' or getmetatable(v)) and self._ or self._all)(self, v, a) or self end,--internal helper, do not list in doc
		_str = function(self, ...) return getmetatable(self).__tostring(self, ...) end,
		_in = function (self, ...) return rawset(getmetatable(self)(...), next, self) end,--Module:Buffer objects use [next] rather than 'parent' to avoid traversal by mw.html.allDone()
		_out = function(self, ...)
			if self[next] then
				local n, parent = select('#', ...), self[next]
				if n > 1 then
					local k, outs, seps = 1, getmetatable(self)[select](n, ...)
					while outs > 0 do
						parent:_(self(seps[k]))
						self, parent, k = parent, parent[next], k + 1
						outs = parent and outs - 1 or 0
					end
				else return parent:_(self(...)) end
			end
			return self[next] or self
		end,
		_c = function(self, clear, copy)
			assert(type(clear)=='table', copy and
				"Buffer:_c(clear, copy) only accepts tables for arg 'clear'."
				or "Two possible causes: 1) Called Buffer:_c(clear, copy) without colon, or 2) Type of 'clear' is not 'table'."
			)
			for k in next, clear do clear[k] = nil end
			local meta, copyMeta, copyType = getmetatable(self)
			if copy then
				if type(copy)=='table' then
					for k, v in next, copy do clear[k] = type(v)=='table' and mw.clone(v) or v end
					clear[rawset] = clear[rawset] or math.huge
					copyMeta = getmetatable(copy)
					if copyMeta then
						if not buffHTMLmeta then meta():_inHTML() end
						if copyMeta==buffHTMLmeta[mw.html] then
							copyMeta = buffHTMLmeta
							setmetatable(clear.nodes, meta)
						end
					end
				elseif meta[type](copy) then clear[1] = copy end
			end
			setmetatable(clear, copyMeta or meta)
			return self
		end,
		_parent = function(self, s, outs, ...) return self:_(outs and self[next]:_str(s, outs, ...) or self[next](s)) end,
		getParent = function(self, ...) return (assert(self, ' (:function(), not .function())')[next] or rawset(rawset(self, next, getmetatable(self)())[next], require, {self})):_(...) end,
		_inHTML = function(self, ...)
			local HTML, meta = mw.html.create(...), getmetatable(self)
			if not rawget(_G, 'buffHTMLmeta') then
				local mwHTMLmeta = getmetatable(mw.html.create())
				buffHTMLmeta, mwHTMLfunc = rawset(mw.clone(mwHTMLmeta), mw.html, mwHTMLmeta), mwHTMLmeta.__index
				for k, v in next, {
					[rawget] = function(HTML, Buffer, ...)
						buffHTMLmeta[require] = HTML
						if ... then return select('#', ...)==1 and Buffer:_(...) or assert(Buffer[...], ('" %s " does not match any Module:Buffer function'):format(tostring(...)))(Buffer, select(2, ...)) end
						return Buffer
					end,
					getParent = function(HTML, ...) return HTML[rawget](HTML, HTML:allDone()[next], ...) end,--return to buffer that created it
					getBuffer = function(HTML, ...) return HTML[rawget](HTML, buffHTMLmeta[next], ...) end,--return to last buffer used
					_out = function(HTML, ...) return HTML:getParent():_(HTML):_out(...) end,
					_inv = function(HTML, s, outs, ...) return HTML.nodes:_(outs and HTML:getParent():_str(s, outs, ...) or HTML:getParent()(s)) end
				} do buffHTMLmeta.__index[k] = v end
				if new_G then
					for k, v in pairs(gfuncs) do buffHTMLmeta.__index[k] = v end
					gfuncs = nil
				end
				local mbpairs, outFuncs, htmlArg, nonSelf, spinach = meta.__pairs, {}, {parent = true, selfClosing = true, tagName = true}, {tag = true, done = true, allDone = true}
				spinach = {--spinach makes html.nodes buffer... get it?...
					[rawset] = math.huge,
					_out = function(nodes, ...) return nodes:getParent('_', nodes:getHTML()):_out(...) end,
					_add = function(nodes, t)--children are unenhanced
						local HTML, nodeFunc = nodes[next], buffHTMLmeta[require].nodes
						for k, v in mbpairs(t) do buffHTMLmeta.__call(HTML, v~=true and v) end
						for k, v in mbpairs(t, false) do
							if htmlArg[k] then HTML[k] = v
							elseif v and v~=true then
								if nonSelf[k] then
									if k=='tag' then
										if tostring(v)==v then mwHTMLfunc.tag(HTML, v)
										elseif v[1] and v[1]~=true then
											local tag = mwHTMLfunc.tag(HTML, v[1])
											setmetatable(nodeFunc._add(rawset(setmetatable(tag.nodes, meta), next, tag), v), nil)[1], tag.nodes[next] = false
										else nodeFunc._add(HTML.nodes, v) end
									else buffHTMLmeta.__call(mwHTMLfunc[k](HTML), v) end--k = done/allDone
								elseif mwHTMLfunc[k] then
									if k=='node' or type(v)~='table' then mwHTMLfunc[k](HTML, v)
									else
										for _, y in mbpairs(v) do
											if y and y~=true then mwHTMLfunc[k](HTML, y) end
										end
										for x, y in mbpairs(v, false) do
											if y and y~=true then mwHTMLfunc[k](HTML, x, y) end
										end
									end
								else nodeFunc[k](HTML.nodes, v) end
							end
						end
						return nodes
					end
				}
				function makeNodesBuffer(HTML)
					local nodes = setmetatable(HTML.nodes, meta)
					nodes[next], nodes.parent = HTML, HTML
					for k, v in next, buffHTMLmeta.__index do
						nodes[k] = rawset(outFuncs, k, outFuncs[k] or function(nodes, ...) return v(nodes[next], ...) end)[k]
					end
					for k, v in next, spinach do nodes[k] = v end
					nodes._inHTML, nodes._build = nodes.tag
				end
				for k in next, nonSelf do--any HTML objects returned by these funcs will be granted Module:Buffer enhancements
					local func = mwHTMLfunc[k]
					buffHTMLmeta.__index[k] = function(t, ...)
						local HTML = func(t, ...)
						if not (HTML.selfClosing or HTML[next]) then makeNodesBuffer(setmetatable(rawset(HTML, next, t), buffHTMLmeta)) end
						return HTML
					end
				end
				for k, v in next, {--add new Buffer object functions
					getHTML = function(self, ...)
						buffHTMLmeta[next] = self
						if ... then 
							if type(...)=='table' then return buffHTMLmeta[require](...)
							else return assert(buffHTMLmeta[require][...], ('" %s " does not match any mw.html function'):format(tostring(...)))(buffHTMLmeta[require], select(2, ...)) end
						end
						return buffHTMLmeta[require]
					end,
					_html = function(self, ...) return self:_(self:getHTML(), ...) end
				} do meta.__index[k] = v end
				buffHTMLmeta.__call = function(h, ...) return ... and ('table'==type(...) and spinach._add or h.nodes._)(h.nodes, ...) or h.nodes end
			end
			makeNodesBuffer(setmetatable(HTML, buffHTMLmeta))
			rawset(setmetatable(HTML, rawset(buffHTMLmeta, require, HTML)), next, self)
			return self:getHTML()
		end,
		_build = function (self, nodes) return table.insert(nodes, self()) end--for compatibility with regular mw.html objects
	},
	__call = function(t, ...)
		local t2 = t[rawset] and t[rawset] > #t and getmetatable(t)():_all(t) or t
		if select('#', ...)==0 then return rawset(t, 'last_concat', t.last_concat or table.concat(t2)).last_concat end
		return table.concat(t2, ...) or ''
	end,
	__tostring = function(t, ...)
		if type(t)~='table' then return t end
		local n, r = select('#', ...), getmetatable(t)
		if n > 1 then
			local k, outs, seps = 2, r[select](n, ...)
			r = r(t(seps[1]))
			while outs > 1 do
				r:_(t[next](seps[k]), 1)
				t, k = t[next], k + 1
				outs = t[next] and outs - 1 or 0
			end
			return table.concat(r, seps[k]) or ''
		end
		return t(...)
	end,
	__concat = function(a, b, Buffer)
		if Buffer then return error end
		Buffer = getmetatable(a)
		local us, them = pcall(Buffer and Buffer.__concat, false, next, 1)
		if not us or them~=error then Buffer = getmetatable(b) end
		return Buffer():_all{a, b}()
	end,
	__pairs = function(t, ...)--pairs always iterates in order
		if not map_i then
			local mk = {__mode='k'}
			map_i, map_n, map_o = setmetatable({}, mk), setmetatable({}, mk), setmetatable({}, mk)--reduces table lookups
			function mBuffIter(t, i) i = map_i[t][nil==i and 1 or map_n[t][i]] return i, t[i] end
			function mBuffOther(t, i) i = map_o[t][nil==i and 1 or map_o[t][i]] return i, t[i] end
			function mBuffMap(t)
				local ti, tn, to, n = {}, {}, {}, #t--reduces table lookups
				map_i[t], map_n[t], map_o[t] = ti, tn, to
				for k = 1, n do
					table.insert(ti, k)
					tn[k] = k + 1
				end
				if n==0 or next(t, n)~=nil then
					local start = next(t)==1 and n or nil
					for k, v in next, t, start do
						if tn[k] then--no op, since: t={[2]=1,[1]=2}; #t => 2; next(t,#t) => 1,2
						elseif tonumber(k)==k then table.insert(ti, k)
						elseif k~=t and v~=t then
							table.insert(to, k)
							to[k] = #to + 1
						end
					end
					if #ti~=n then
						table.sort(ti)
						for k, v in next, ti do tn[v] = k + 1 end
					end
				end
			end
		end
		if select('#', ...)==0 or ...==true then mBuffMap(t) end
		return ...==nil and mBuffIter or mBuffOther, t
	end,
	__ipairs = pairs
},{__tostring = function()return''end,
	__call = function(self, ...)
		if not rawget(_G, 'libFunc') then--adds string and mw libraries; also add global functions if passed; rawget avoids error when reading nil global
			rawset(_G, 'libFunc', {__call = function(t, self, ...) return t[1](type(self)~='table' and self or self:_str(), ...) end})
			for _, library in ipairs{mw.text, string, mw.ustring} do
				for k, func in pairs(library) do self.__index[library==mw.ustring and k .. '_' or k] = setmetatable({func}, libFunc) end
			end
			rawset(_G, 'new_G', ... and type(...)=='table' and (...)._G==... and ...~=_G and (...).mw and (...).mw.log==mw.log and ...)
			if new_G then
				rawset(_G, 'gfuncs', {--stored as global for Buffer:_inHTML
					_G = function(self, name, var)
						if rawget(new_G, name) then 
							local metaG = getmetatable(new_G)
							if not metaG then
								metaG = {__index = {}}
								setmetatable(new_G, metaG)
							end
							metaG.__index[name] = new_G[name]
						end
						new_G[name] = var or self
						return self
					end,
					_R = function(self, name, var) new_G[name] = var return self end,
					_B = function(self, var) return var or var~=false and self end
				})
				for k, v in pairs(gfuncs) do self.__index[k] = v end
			end
		end
		if new_G and ... and (...)==new_G then--avoid error when passing _G to buffer a second time
			local new, name = setmetatable({}, self), select(2, ...)
			if name and type(name)=='string' then new_G[name] = new end
			return new
		end
		return setmetatable({}, self):_(...)
	end,
	__index = function(t, i)
		return function(...) return t.__index[i](t(), select(2,...)) end
	end
})