MyWikiBiz, Author Your Legacy — Thursday July 25, 2024
Jump to navigationJump to search

Template:Module rating Template:High-risk Template:TOC right Template:Module rating This was originally developed to optimize string concatenation as a helper method within Module:Asbox, but has been generalized for all modules.

The interface for Module:Buffer objects is similar to that of Template:Luaref objects in that you may build complex strings with independent child nodes. In most cases, you may use Buffer objects like a normal string, including using Template:Code operator. See also: #String, mw.ustring, and mw.text libraries

Additionally, there are several specialized forms and extended objects, described further in their respective sections:

Last but not least, this module has an ordered __pairs which can be more thorough than Template:Luaref and Template:Luaref. (Even reads nil keys!) The logical uniqueness of this iterator may be reason enough to Template:Luaref Module:Buffer.





Creates a new Module:Buffer object when the module returned by Template:Luaref is called as a functionTemplate:--i.e., there is no 'main'.

Because Template:Luaref are a pet peeve of this developer, this forwards arguments to Template:Luaself; if that op is not desired, you may chain a Buffer object function directly to the module and the self-action will be redirected to a new Buffer objectTemplate:--i.e [[#Buffer:_inHTML|Template:Code]] is equivalent to Template:Code.[note 1]

The global variable Template:Luaref is "magic" when passed as the first arg. Such enables the global functions and, if followed by a name string, will declare the new Buffer with that name in the global scope. If the argument following name is a table with no metatable, it and any other Template:Luaref are forwarded to Template:Luaself; otherwise, as long as the first vararg is not nil or false, this passes them to Template:Luaself.[note 2] The _G passed may also gain a __call Template:Luaref (details at _G object).

Template:Anchor As a final note, if you Template:Luaref text from a module which returns a Buffer object, it may be more efficient to create a new Buffer via chaining Template:Luaself after a require statement for the other module and use Template:Luaself at the point where you would append the required text. (Best add Template:Code so future editors won't hunt for Template:Code at the other module)

Basic functions


Buffer object

Template:Luaself Template:Hatnote

Get a Buffer as a Template:Luaref via a function call on the Buffer object (as opposed to a call on the module). This is basically shorthand for Template:Luaref, or, with no args, Template:Luaref. However, if this Buffer is in raw mode[note 3] or contains at least one Template:Luaref index, this reconstructs the Buffer by creating a new table, coercing it contents to strings and appending them sequentially to the temporary "buffer" via new-Template:Luaself (or a similar process).

Unconventionally, any Template:Luaref-type position passed as i or j are treated as relative to length; that is, Template:Luaself is equivalent to Template:Luaself (which obviates the need to Template:Luaref Buffer just to use the Template:Luaref). Moreover, unlike table.concat, this automatically adjusts numerical[note 4] positions to be within the range of the lowest and greatest indicies.

Note you may append a Buffer object without tostring coercion to an Template:Luaref object via Template:Luaref (though not mw.html:wikitext because of type checking).


When strung without a (valid) sep, the result is cached at Buffer.last_concat. Until purged, future calls to return that Buffer as a string will return this index instead.[note 5]

This should clear automatically whenever a Buffer object function changes the contents of a Buffer. You may manually purge the cache by setting this key to nil, as well as by passing nothing to Template:Luaself.


Template:Luaself Template:Hatnote

Appends a value to the Buffer. In rough terms, Template:Code is the same as Template:Code. (It may help to imagine Template:Code as a Template:Code that has stood up and is now casting a shadow.) Template:Anchor

If passed an invalid Template:Code listed below, this is a no-op:

A table with no __tostring will pass through Template:Luaref before insertion. An Template:Luaref may be thrown if the table would cause table.concat to error. (Use Template:Luaself instead for such tables.)

When passed Template:Code of type Template:Luaref, the argument is identical to pos for Template:Luaref. In fact, assuming a valid value, Template:Luaself is exactly the same as Template:Luaref.

Just like with the position arguments of Buffer(), any pos of type Template:Luaref would be treated as relative to length. Template:Anchor

Set Template:Code to true to force append a value without tostring coercion, including invalid values. If given only two (non-self) arguments with the second being a boolean, then the second is read as raw instead.



Removes the value buffered at Template:Code. As with Template:Luaself, a string pos string is treated as Template:Code.

If a non-boolean Template:Code is provided, then it will replace the value indexed at pos. Passing a boolean as the second argument is a no-op.

When replacement is nil, the op is basically Template:Luaref. As with the positional arguments of other Buffer methods, any numerical string pos is added to length, such that Template:Luaself removes the last item. Note the only type check on replacement is a direct comparison to both booleans (nil is implied). Any other type, including strings, sets the Buffer to raw mode.

A pos that is omitted, nil, or false has the same effect as though '0' (or Template:Luaself) were passed. Given only one non-numerical argument which evaluates true but Template:Luaref would return it as nil, this treats it as though it were passed as the second. If passed a non-numerical pos with any other argument, including nil, this is effectively a no op (though may still purge the cache).



Takes a table Template:Code, iterates through all number keys Template:Luaref, appending each valid value to the end of the Buffer. In contrast to Template:Luaref, this starts at the most negative key (down to Template:Luaref) and ends at the most positive index, continuing through any nil keys and includes non-integer number keys.

A table value that has no metatable will have its contents iterated by this function before moving on to the next value. All other data types are processed by Template:Luaself. Template:Anchor

By default, this ignores non-number keys unless Template:Code evaluates true. If so, non-number keys are processed after number keys. Keep in mind such keys are iterated in Template:Luaref order, though an order may be imposed by wrapping each pair in a table indexed at a number key.

If given a value = pos pair, defined as a number or number string indexed at a non-number key, then they will be passed as the Template:Code and Template:Code arguments for Template:Luaself. Thus,


produces the same result as:

<syntaxhighlight lang="lua">Buffer:_(1):_(2):_(3)

if variable then Buffer:_(4) else Buffer:_'four':_('zero',1)--vs :_all{four='1',zero=1}; less redundant would be end -- :_all{'four',zero=1}, but that doesn't demo string numbers Buffer:_'... done' --returns "1234... done" if variable evaluates true or "zero123four... done" if false</syntaxhighlight>

If a non-number key points to a value that cannot be coerced into a Template:Luaref then the pair may be treated as functionName = args, when functionName matches a Buffer object function and args is not boolean. If args is such that Template:Code evaluates true, then this will pass the return of Template:Luaref to the named function; otherwise, the value is passed as is.[note 6]



Passes any arguments to Module:Buffer to create a new Buffer object, sets an external reference to the parent Buffer and returns the child.[note 7]

This does not append the child to the parent. (See Template:Luaself)

Also, be aware that Buffer parent references are Template:Luaref. Thus, if you were to (re-)set a local variable that is currently set to the parent, such could trigger immediate garbage collection on the parent.



Joins a Buffer with Template:Code and appends result to its parent, which is returned. If no parent is found, this is a no-op and returns the same Buffer.

When given two or more arguments, this reads the first as Template:CodeTemplate:--the number of :_out() operations to perform.[note 8] This applies the first sep in Template:Code for the current Buffer, the second for its parent, the third for its grandparent, and so on.

If the last item is a Template:Luaref, then any nil in sep-list default to table[1]; any false mean "no-sep".[note 9] The table may be the second arg (i.e. sep-list may be omitted). If it has other keys, then table[n] would apply instead of table[1], making these synonymous:
Template:Luaself and Template:Luaself.

The number Template:Code is "magic" when passed as the first arg (even by itself), joining and appending to the same Buffer after it has been Template:Luaself. This is the only method by which a Buffer in raw mode may lose that status. Parent references are preserved.



Joins a Buffer with Template:Code and returns the string.

This uses the same helper method as Template:Luaself to handle multiple arguments, with which, if provided, this creates a new temporary Buffer to which this appends the results of the number of generations specified by ops, with each ancestor in front of its descendants. This then performs one additional concat op using the sep at Template:Code and returns the result. If a parent-less Buffer is reached before ops, then the sep that follows its op number will separate the generations.

When no valid sep is given, this returns a string identical to what would append to the next ancestor, if those arguments were passed to Buffer:_out instead and one additional :_out() op performed.

However, because this does not actually append child Buffers to their parent, the result may differ where this would insert the sep at ops + 1 and Buffer:_out would place the parent's sep between it and its child instead.



Nils all keys of the table referenced by Template:Code and unsets its metatable. If clear evaluates false, this simply purges the cache at Template:Luaself.

If given a table to Template:Code, this will duplicate all key-value pairs of copy into clear, cloning any table value recursively via Template:Code. This returns the Buffer unless passed the number Template:Code as clear, which causes this to create a new table and return that instead. Passing Template:Code as copy is equivalent to passing the Buffer itself. If copy is not a table, then it will be set as the first item in clear as long as it is not false.

While this may resemble Template:Luaref, there are several differences, namely that this:

  • Gives clear the same metatable as copy (or sets meta, if given) as opposed to a "clone" of the metatable.
  • Conserves Template:Luaref attribute (though empty strings may replace some nil keys[note 10])
  • Template:Luarefs values and iterates without invoking any __pairs Template:Luaref.
  • Includes Buffer parent and raw attributes (stored externally)

To obtain the key-value pairs left as empty strings in the previous copy op in a table, simply call this again such with value such that rawequal(clear, copy) is true; call :getParent() on this table returns clear (useful after leeaving the local scope which referenced clear).


Template:Luaself Template:Hatnote

Resembling the reverse of Template:Luaself, this calls Template:Luaself on the Buffer's parent with the arguments given and appends the strung ancestor(s) to the current Buffer, which is returned.

The parent is unaffected by this operation and may still be retrieved via Template:Luaself or re-appended again with this function.


Template:Luaself Template:Hatnote

Returns parent Buffer, or, if none exists, creates a new Buffer and returns it as the adopted parent. As with Template:Luaself, this does not automatically append the adoptive child to the new parent.

Pass a non-false value and this performs an op on the parent object.

If passed anything other than value (including nil), this requires that value names a function available to the parent object, which this calls and forwards the additional Template:Luaself.

Pass only a table value which has no metatable and this forwards value to the parent which calls Template:Luaself.

Given only a string starting with Template:Code and naming a parent function, this calls it on the parent without arguments. Any other valid singular argument appends to the end of the parent Buffer.[note 11]



Unsets Buffer parent reference.

If passed any args, they are forwarded to the current parent, if one exists, via Buffer:getParent as a "parting gift". In either case, returns the current Buffer.

This is not necessary for Template:Luaref since Buffer parent references are weak. Rather, use this when it is desirable to assign a new parent via Buffer:getParent or, for example, to prevent the inclusion of an ancestor when passing Template:Luaref as ops for functions such as Template:Luaself (more useful when recycling Module:Buffer from another Module).

Stream mode



Switches a Buffer to stream mode. While streaming, the __call metamethod will append values to the end of the Buffer instead of the usual op.

Aside from that, there is only one other function: Template:Luaself. Any args passed to Buffer:stream are forwarded to that function for a reason that should be evident when you finish reading this very short section.

No special action is needed to exit this mode. The normal call to string op is restored upon the use of any regular Buffer function or any operation which coerces the Buffer into a string (e.g. the .. operator).


Stream-Buffer object


Stream-Buffer objects accept only one argument which they append if valid. That is, the op is a streamlined version of Template:Luaself sans the pos and raw args.

This also exploits Template:Luaref to append a series of string literals (and tables) with nothing between them (or only Template:Luaref if desired).

For example, both A and B will produce identical strings:

<syntaxhighlight lang="lua">local A = require'Module:Buffer':stream'A string of text may flow''with nothing between each string' 'or perhaps only a space'

'or even tab and line-break characters''and continue to append individually''for use with a joiner' local B = require'Module:Buffer':_'A string of text may flow':_'with nothing between each string' :_ 'or perhaps only a space' :_'or even tab and line-break characters':_'and continue to append individually':_'for use with a joiner' mw.log(A==B, A:_str' ') true A string of text may flow with nothing between each string or perhaps only a space or even tab and line-break characters and continue to append individually for use with a joiner </syntaxhighlight>



Appends an undetermined number of valid values to the Buffer object.[note 12]

If the above line gives you Déjà vu, that is because it is drawn from Template:Luaref. However, unlike mw.html:wikitext, this does not stop at the first nil value.[note 13]

HTML library extension

Upon the first call to Template:Luaself, Module:Buffer clones the Template:Luaref, adding Module:Buffer's __eq and __concat metamethods along with a few additional functions.

Objects with this modified metatable are referred to as Buffer-HTML objects. Yet, though dressed in bells and whistles, they are only named with Buffer as an adverb since they lack most Buffer object functions.

In contrast, the Element-Buffer (returned by the function call on a Buffer-HTML object) is a true Buffer object with specialized "tricks" allowing complex structures to be built via both mw.html and Buffer object methods as well as through a builder that perhaps marries the best of both worlds.

Buffer functions for HTML



Accepts the same parameters as Template:Luaref to create and return a modified mw.html object. As with Template:Luaself, this does not append the child object to the parent Buffer and instead sets a Buffer-style parent reference.

An exception to the above is when chaining this to an Element-Buffer and such produces a selfClosing tag; when both conditions are met, this appends the tag and returns to the same Buffer.[note 14]

Unlike mw.html.create, if Template:Code has keys other than args.parent and args.selfClosing, it will pass for further processing through Template:Luaself (a cousin of Template:Luaself that handles mw.html functions). Moreover, if passed a table where mw.html.create expects tagName, this treats it as args instead.

Finally, this does not automatically set the mw.html.parent reference, making this an alternative to mw.html:tag



Available only after Template:Luaself is used the first time. Template:Anchor

Accepts the same arguments as Template:Luaself, however this instead return the last Buffer-HTML object ("lastHTML") created, or, if available, the lastHTML passed to any of the following functions:



Available only after Template:Luaself is used the first time.

This (re-)appends the last Buffer-HTML object to the current Buffer object. The raw and pos args are generally the same as those in Template:Luaself.

When called with no arguments on an Element-Buffer, this appends lastHTML without string coercion. Be warned however that if the Element-Buffer belongs to lastHTML or one of its tags, such will cause an infinite loop, which can be avoided by passing an explicit nil to append lastHTML as a string.[note 15]

HTML object functions

Buffer-HTML objects may be used like any mw.html object. (In fact, merely replacing Template:Code with Template:Code in an existing Module should produce the same output.)

Template:Anchor Most mw.html functions are unchanged, except Template:Luaref, Template:Luaref, and Template:Luaref are embedded in a wrapper function that checks whether they return a normal mw.html object. If so, switches the metatable to convert it to a Buffer-HTML object and sets a parent reference. [note 16]

As a side bonus, the Template:Code may be used on Buffer-mw.html objects directly (no Template:Luaref needed).


Buffer-HTML object


Call this object as a function to return its Template:Code index[note 17], which this converts to an Template:Luaself object, granting it the same metatable as regular Module:Buffer objects (as well as several additional "tricks") and assigning the Buffer-HTML as its parent Buffer.[note 18]

This takes one argument which is forwarded to its Element-Buffer. Tables pass to the specialized HTML builder Template:Luaself. All other #valid values are appended to its Element-Buffer via Template:Luaself.

Template:TOC tab

You cannot chain call regular Buffer functions on a Buffer-HTML object; however, since mw.html functions cannot read Buffer-style parent references, modified versions of methods that return the parent Buffer are available to Buffer-HTML without having to call into the Element-Buffer. For convenience, Template:Luaref is called automatically prior to the op, though after the lastHTML reference has been set for Template:Luaself.[note 19]

In addition to the above, global functions may be available to Buffer-HTML if enabled; these functions are the same for all Module:Buffer objectsTemplate:--i.e. the self action is never redirected.

Element-Buffer functions


Element-Buffer object


Sharing the same metatable as with regular Buffer objects, Element-Buffers concatenate the same way when called to produce a string analogous to the JavaScript DOM "innerHTML" property. In other words, when strung, it is generally the contents of the Buffer-HTML object without the "outerHTML" or tag.

There are exceptions to this "innerHTML" behavior. For instance, as appended to another object via Template:Luaref, an Element-Buffer and its Buffer-HTML are interchangeable (though appending the former via Template:Luaself only includes the inner result).

Also, using the concatenation operator Template:Code on an Element-Buffer includes its tag in a manner depending on if it is selfClosing:


You may use most Buffer object functions normally, however if there is a Buffer-HTML version, it instead behaves as though chained on the outer HTML object.[note 20] You may also chain any mw.html object function. Unless otherwise indicated, such returns a wrapper method that merely redirects the self-action to the outside Buffer-HTML.[note 21]

As a final note, Element-Buffers are in permanent raw mode since it is expected that some mw.html method (e.g. :tag and :node) may or will append non-string elements.



When passed nothing, this should behave just like Template:Luaref as called the "outer" HTML objectTemplate:--returning Buffer-HTML.parent, if available, or Buffer-HTML if not.

However, this has been re-designed to accept Template:Code, the number of :done() operations to perform. Thus, Template:Code is equivalent to Template:Code.

Pass Template:Code (zero) as dones to return to the Element-Buffer's direct HTML container.

Finally, keep in mind that Buffer-HTML objects use the original mw.html:done (albeit in a light wrapper).



This uses the same helper method as Template:Luaself to handle arguments and produce new Buffer-HTML objects, selectively passing args to Template:Luaself when it contains keys not used by Template:Luaref.

As may be expected, this differs from Buffer:_inHTML in that this actually appends the tag and will set a mw.html-style parent reference. This also lacks the other function's "auto-done" feature for selfClosing tags.

As with the other Element-Buffer remake of an mw.html method, the features described here do not apply to the version used by Buffer-HTML objects.



Takes a table as its only argument. This then thoroughly iterates all number keys from lowest to highest using this module's custom __pairs method. Most values append as wikitext if valid. If a table is indexed at a number key, this recursively iterates the table before moving on to the next key.

After processing all number key-value pairs, this then iterates the other (non-number) keys. For those naming a core Buffer object function, this selectively unpacks Template:Code in a manner described at Template:Luaself when that function is passed the nanKey parameter (excepting that this does not read numbers as pos, i.e. treats them the same way as strings).

This also accepts keys naming HTML and global functions as well as mw.html arguments. Thus, Template:Code appends a BR tag after the text and Template:Code appends the BR before the text. Note however that how this handles args for such keys depends on the particular function or argument named:


The effect of passing args with keys such as args.selfClosing and args.parent is the same as though args were passed to Template:Luaref. This also takes one additional arg, i.e. args.tagName, which value replaces the original tagName of the HTML object (or, if false, removes the tag).

Note that these are the only keys for which a boolean arg would result in an op. (For Buffer object functions that do not no-op when passed only a boolean, place the boolean in an args table for unpacking.)


A non-number key and value pair may default as the cssName and cssValue parameters for Template:Luaref when the key matches none of the three argName keys nor the name of any available function for Buffer and mw.html objects.

This sends non-boolean cssValue though Template:Luaref prior to forwarding it to mw.html:css. Because this is the default, any typoed key goes to mw.html:css as cssName. Names of functions not yet loaded also end up there.

For convenience, any _ character in the key string is automatically substituted with the - character; thus Template:Code is equivalent to Template:Code.

The form Template:Code also works (or to clear a previously set value; see example at args.htmlFunction for more details).



Set the key args.tag to a string and this calls Template:Luaref with it as the tagName argument. This then raw inserts the returned mw.html object ("tag"), emulating the effect of Template:Luaref minus parent references, which are unnecessary.

Pair the args.tag key with a table value and this calls mw.html.create with Template:Code as tagName (or nil if #invalid), appending it as described above for string values, but also pointing tag.parent to the Element-Buffer's parent as well as temporarily setting the tag as the parent Buffer of tag.nodes. This then treats tag.nodes as a pseudo-Element-Buffer, recursing tag.nodes as "self" and the table as args, though only iterating keys not equal to Template:Code (or less).[*]

Note this appends normal mw.html objects. That said, most Buffer functions named in args-list should still work as though the tag and tag.nodes were Buffer objects.[note 22]

Upon completing a recursive iteration for args.tag, this checks if the tag is selfClosing, in which case, this sets tag.nodes to nil. Likewise, if its tagName property evaluates false, this nils tag.styles and tag.attributes. Such presumes those properties will not be modified afterwards, so use mw.html:tag outside of Element-Buffer:_add if such is not the case.



Similar to args.tag, this treats the first index of the table as the ops argument of Template:Luaself. After calling that function, this then iterates all subsequent keys in a recursive call on the Element-Buffer of the Buffer-HTML object returned.


Similar to the previous two, except that the first index is not used as an argument; thus, the entire table is iterated.



If the global functions have been loaded and a key matches one, this calls the function with the first two arguments Template:Luarefed from the paired args-list table. This then recursively iterates the list, excluding keys less than or equal to Template:Code, with whatever object is returned as self. However, if the returned object has a metatable and object.nodes exists, the self will be object.nodes instead.

Because Template:Luaself takes only one argument, args._B only unpacks the first index and starts the iteration after that key.

If neither of the first two keys evaluate true, this assumes the paired value is a string for use as the name argument for the function matching its key.[note 23] In that case, the current call stack's self (an Element-Buffer or tag.nodes if this was called indirectly) is self for the global function.


If args.key matches an Template:Luaref that does not have its own args section, this checks if the associated object is table. If not a table, or if object.nodes evaluates true, this calls the mw.html function with the object as the only argument.

For table objects without an object.nodes, this iterates the table (non-recursively), repeatedly calling the named mw.html function with one or two arguments depending on key's type in each loop. Non-number key-value pairs are both passed as arguments. For numeric indices, only the value is passed. Boolean values are a no-op.

Unlike with most implementions of Module:Buffer's __pairs, this first loops through non-number keys, followed by number keys (still ordered from lowest to highest). Thus, something like Template:Code is equivalent to Template:Code, setting then unsetting the width attribute and returning the Element-Buffer for a net no-op (though the practical purpose of such is a mystery for this developer).

For args.css only, this auto-replaces underscores with hypens for string keysTemplate:--i.e., you may save four keystrokes/pair by typing css_property = instead of ["css-property"] =. This does not apply to strings indexed at number keys.


Loadable convenience extensions

The methods here are loaded on demand or depend on subroutines which need initialization.

These methods can greatly simplify the structure of the modules which employ them by doing, in a fluent manner, many tasks which may otherwise force an awkward interruption in Buffer call chains.


Global functions

Methods such as Template:Luaref and Template:Luaself traverse a node tree in only one direction. While fine for returning to an ancestor, they do not provide navigation to a non-ancestor (often necessary for templates with co-dependent parameters). Yet, repeated breaks in call chains to set local variables for several nodes of the same branch can look choppy if not confusing for nodes many generations removed from its declaration statement.[note 24]

Templates with several conditionally-appended nodes with similar, but not identical, parts may present another conundrum for coders who must decide between having awkward call chain interruptions to store potentially repeated components as local variables or constructing a somewhat redundant module that is more susceptible to maintenance errors by future editors who may patch one code segment but miss the sibling buried within a convoluted nesting of Template:Luaref.

This module's global functions and added syntactic sugar for the _G object were formulated to simplify such node trees with multi-conditional or repeating structures by providing concise in-chain variable declaration. The extension is enabled by passing your global table to the moduleTemplate:--either in the initial call to require'Module:Buffer' (more instructions in that section) or to Template:Luaself which forwards arguments to Module:Buffer.[note 25]


Template:Luaself MyWikiBiz 16x16.png

Pass name and save to assign the object passed as save to a global variable via Template:Luaref.[note 26]

Pass only name and this substitutes self for save to assign the Buffer object to _G[name] instead. Give an explicit nil as save to unset a global. This returns the Buffer object as well as any argument given after name.

This is a no-op when name is nil or a boolean, or, when save (eventually) evaluates true and Template:Luaref also returns true. Template:Anchor

If the named global already exists, this "backs up" the old value by moving it to the meta __index of the global table, setting a new metatable if none exists.[note 27] Retrieving the old value requires unsetting the new one via Template:Luaself (more details in that section). If overwritten a third time, the first value is discarded and the second takes its place in the back up.

If a metaglobal variable exists but the global is nil, this sets the global without unsetting the metaglobal (i.e. does not back up a nil global). An exception is when this is given an explicit nil as save and only the metaglobal exists; thus, passing nil twice for the same name, unsets the key in both the global table and its metaindex.



This Template:Luaref with the global table as the first argument and name and save as the second and third, respectively, returning the Buffer object for call chaining.[example 1] This is a no-op if name is nil or a boolean.

Template:Anchor Template:See Note that Buffer methods use a local variable new_G as a proxy for the global table _G; though not a global index, the string Template:Code is a "magic word" that changes the destination for future save for this and Buffer:_G.

Pass a table as var (same place as save) to set as the new new_G. Any table such that Template:Code is treated as a (former) new_G object. This Template:Luaref of former proxies and Template:Luaref with the Template:Luaself __call method on non-new_G tables. Then, this, if third parameter metaindex equals:

  • nil Template:-- backs up the current proxy as the metaindex of the next (though this no-ops if var equals new_G to avoid cyclical indexing).
  • false Template:-- leaves the metaindex intact (replacing the current proxy without back-up)
  • true Template:-- unsets the metaindex of the next proxy
  • any other value Template:-- sets that value as the metaindex of the next proxy. (Note new_G._G is not set until it is returned by Template:Luaself)

To omit or to pass nil/false as var has the same effect as Template:Luaself. Pass true instead and this treats it as though passed as metaindex, creating a new proxy without backing up the incumbent.



This returns the value indexed at key name in the global table. If it does not exist, this forwards both arguments to Template:Luaself and returns the saved value (which may be itself).

In other words, Template:Luaself is roughly equivalent to Template:Code.

The string Template:Code will return the Module:Buffer local variable new_G, used as a proxy for the global variable _G. Given more than one argument, this forwards arguments to Template:Luaself to assign another proxy global before returning the (newly-deposed) proxy. This then sets new_G._G to the original _G object for call chaining. (See § chain call in _G object).



Takes only one argument and returns that argument.

Assuming the only X declared is Template:Luaref and new_G equals _G, then Template:Luaself and Template:Luaself are equivalent.[note 28]

When passed a variable that does not exist, this returns the Buffer nil object:


Buffer-nil object


The Buffer-nil object is unique. Calling this as a function returns nothing (in contrast, calling an empty Buffer object returns an empty string). This does however have the Module:Buffer __concat metamethod, which treats this the same way as any invalid object (i.e. ignores it).

Appending this via Template:Luaref or Template:Luaself has the same effect as appending nil. Passing this to Template:Luaref returns nil instead of the string 'nil'.

The only real Buffer method in its meta __index is Template:Luaself, however, any non-numerical key string retrieves a function that only returns the Buffer nil object for call chaining. In a sense, you can think of Template:Code as an Template:Code block around the following chain that ends in the next :_B().

If Template:Luaref, the clone will be a normal Buffer object.


_G object

The first _G variable passed to this module is given a __call metamethod that self-Template:Luarefs and returns in a manner that depends on whether it was called directly or from a chain.[example 2] This module conserves any pre-existing metatable and alters no metamethod other than __call.

direct call

_G( k, v )

When called, the _G object self-sets any string passed as k with whatever is passed as v. This returns v, or nil if omitted (unlike with rawset, an explicit nil is not necessary to unset a variable with direct calls).

Note that k must be a string to declare or unset a global in this op. Tables passed as the first argument are treated as though this were executed via a call chain (discussed shortly). Passing k which is not one of those two types will throw an error.

chain call

chained-object:_G( k, v )

MyWikiBiz 16x16.png

When used in a call chain, this rawsets the key-value pair in the chained object and returns that object. The _G object may chain itself when returning _G is desired for another op instead of v.

In contrast to the direct op, the in-chain op will index non-string k values. Moreover, this only unsets object[k] when passed an explicitly nil v.

If v is omitted in-chain, this uses the chained object as the missing argument; thus, (chained) object:_G'string' has identical effect and return to _G('string', object).

The same __call method is given to new_G objects created by Buffer:_R, however the direct call only works if its metaindex is the _G object. Any table such that table._G points to the _G object may chain save to itself regardless of metaindex.

Though the behavior of the chain op when v is omitted may be a dead ringer to that of Buffer:_G when save is omitted and new_G is the chained object, mind that the Buffer object function sets keys in new_G variable rather than the chained (Buffer) object; in other words, this is unaffected by Buffer:_R reassigning new_G to another table. Also, this does not have the back up behavior of Buffer:_G.


Buffer-variable object



Raw appends a Buffer-variable object, which may appear as a different value each time the object (or its container) is converted to a string.[example 3]

Initialize a Buffer-variable object by passing as var a:

  • number - which, when strung the first time, appears as that number and reappears as var + change the next time it is strung.
  • string - that transforms into the next ASCII character via Template:Luaref.
  • table - to return the first (non-nil) item, then the second, and so on as determined by Template:Luaref, looping back to the first item after reaching the last. (Note the change argument does not apply to table-based Buffer-variables.)
  • custom function - to be set as the _build and __tostring method of a variable-object, though instructions for coding such functions are beyond the scope of this manual.

Re-append the same variable object by passing Template:Code as the only argument. For non-table-based variables, you may specify change to append a sister object which transforms the value at the rate specified. Changes are cumulative. Thus, if the original is re-strung after a sister, its value will differ from that of its last appearance by the sum of the original and sister rates and vice versa.

Apply a change without appending a new variable object to the Buffer by passing Template:Code. The shift is effective immediately and may affect previously appended variable objects not yet finalized. Pass only false (i.e., omit change) to produce the same effect as stringing the original once. Note that the false-change is the only change table-based Buffer variables will honor.[note 29]

Pass nothing to append a version which simply repeats the result of the last stringing. While generally identical in effect to the object generated by :_var(true, 0), the Buffer-variable will return nothing if strung before any of its sisters.

If passed an explicit nil as the first argument, this is no-op. If passed a boolean before any Buffer-variable has been initialized, this is also a no-op. Note that any op disables future caching at Template:Luaself for all Buffer objects in your module (and in any module which may require it).


String, mw.ustring, and mw.text libraries

Basic usage


You may directly chain any function from the following libraries on Buffer objects:


Functions from these libraries added to the Module:Buffer metatable on-demand and placed within a wrapper method that strings the Buffer object for the first argument and then forwards the remaining arguments.

Thus, the following are equivalent: Template:Code and Template:Luaref

If a name exists in both the string and mw.ustring libraries, the string version takes precedence. You may prefix the letter u on any mw.ustring functionTemplate:--e.g. Buffer:ulen returns the number of unicode characters and Buffer:len returns the number of bytes.

Buffer:gsub and Buffer:ugsub have a slightly different wrapper which substitutes the repl argument of Template:Luaref and Template:Luaref when it evaluates false or is omitted with an empty string (otherwise the originals would throw an error). This saves a few keystrokes when removing characters via Template:Code as opposed to Template:Code. All other arguments are handled the same as with the other on-demand methods.

Library functions which take a non-string as the first argument are not supported.

Empty Buffer interface


To obtain the first return value as a Buffer object (as opposed to whatever type the original normally returns), simply chain the imported method immediately after creating the new Buffer via Buffer:_in. Only empty Buffer objects which have a parent object will append the result of their parent in this manner.

This syntactic sugar allows two things:

vs. the following which has the same order of operations albeit harder to see: Template:Code
Special case: Element-Buffer


The 'empty' behavior is different when chained to empty Element-Buffer or an empty child Buffer of an Element-Buffer.

Library methods chained to an empty Buffer which parent is an Element-Buffer object will instead string the grandparent Buffer-HTML object for use as the first argument before appending the result to the new Buffer. This interface is provided because Buffer-HTML objects, which are not true Buffer objects, are unable to load these functions, making this the only chainable option for Scribunto methods that includes the outer tag of non-empty Element-Buffers.

Chained on an empty Element-Buffer, these methods will string the Buffer object which created its HTML tree via Template:Luaself[note 30] and append the result to the Element-Buffer.[example 4]

Modified Template:Code operator

All "true" Buffer objectsTemplate:--e.g., the regular, stream, and element varietiesTemplate:--share the same Template:Luaref. Some Buffer-like classes, namely Template:Luaselfs and the Template:Luaself, also have this same metamethod.

The extended Template:Luaref operator does not append to Buffer objects. In other words, Buffers generally remain the same as before the op excepting those effects that apply whenever Buffers are strung (See Template:Luaself, Template:Luaself, and Template:Luaself).

with non-tables


Any non-table value may be joined a Buffer object with the concatenation operator Template:Code without error.

With the exception of Element-Buffers (which are a special case), the op passes each object, ordered left-to-right, to Template:Luaself which inserts validated items in a new table, which this returns through Template:Luaref.

Concatenating an invalid value and a Buffer has generally the same effect as Template:Luaref unless such involves:

  • the Buffer-nil object Template:-- which produces an empty string (instead of nil)
  • Element-Buffer objects Template:-- which returns the string of the parent Buffer-HTMLTemplate:See

with tables


The same general operation applies for tables as with non-tablesTemplate:--i.e., validated values are inserted left-to-right into a new table to be joined by table.concat. In fact, tables which have a metatable (including Buffer objects which are not an Element-Buffer) are forwarded to Buffer:_ and processed the same way as non-tables.

Given a table for which Template:Luaref returns nil or false, this instead forwards the table to Template:Luaself, which iterates every value indexed at a number key in sequential order, inserting those which are valid in the new table.

As a reminder, Buffer:_ validates tables with metatables that lack a __tostring method through table.concat, which throws an error on sequences containing one or more value that is neither a string nor a number. Such accounts for nearly all cases of breaking errors involving this op.

Note that the valKey parameter of Buffer:_all is not triggered.

for Element-Buffers


To recap and expand upon § Element-Buffer-object, the behavior of this op depends on whether its parent Buffer-HTML is Template:Luaref or if the other value is also an Element-Buffer. Also, the final result always includes the outer HTML object (i.e., the tag) in some manner.

For Element-Buffers of "open" tags, this op creates a table with a metaindex that references the parent of the Element-Buffer. The table is then effectively a "mirror" of the parent Buffer-HTML object except that it contains an empty table at table.nodesTemplate:-- the index of the Element-Buffer within its parent. This then populates the mirror's inner table with the string of the Element-Buffer and the other value, validated left-to-right, in a manner not unlike what this does with the temporary table it creates when concatenating non-element Buffers to another value. This then returns the mirror table through the __tostring metamethod of the mw.html library, yielding a string which resembles that of the parent tag but with value inserted in front of or behind the original inner text depending on whether value was to the left or right of the .. operator, respectively.

When the selfClosing property of the parent evaluates true, this operates on the parent instead of the Element-BufferTemplate:--i.e., the resulting string will have value on the outside as opposed to within the tag (placing it inside would be pointless since selfClosing tags do not show inner contents).

If both operated objects are Element-Buffers, this mirrors the parent of the first. The inner table of the mirror is then populated by inserting the string of the first Element-Buffer followed by the string the parent Buffer-HMTL of the second. The resulting string would be as though the parent of the second were the last node of the first parent. Note that this Element-to-Element rule does not apply when the first Buffer belongs to a selfClosing tag (in which case, this behaves as though the selfClosing parent were to the left of the operator, returning a string with the selfClosing tag inside the tag of the second Element-Buffer in front the latter's inner contents.)

Finally, this combines an Element-Buffer and a table value which has no metatable by passing the table as args for Template:Luaself with the mirror of the Element-Buffer as the "self". This avoids permanently changing the parent Buffer-HTML by setting a new table at table.attributes or table.styles in the mirror the first time methods such as Template:Luaref, Template:Luaref, Template:Luaref, etc. attempt to access those tables, copying the original's via the recursive form of Template:Luaself. Note however that permanent changes may be made to other objects whenever methods such as via Template:Luaself or Template:Luaself are keyed to navigate beyond the mirror or "sandbox".




Returns two values: an iterator function and the table. This is intended for use in the Template:Luaref for.

One distinctive feature of this pairs method is that it splits keys into two groups: Template:Luarefs and non-numbers. This indexes each group of keys in its own "map" object, traversed by its own iterator functionTemplate:--i.e, iterating both sets of keys requires two separate for loops. Numeric keys are served in an orderly fashion as with Template:Luaref except that those which are negative, non-consecutive, and non-integer may be included. Moreover, this can find some keys paired with explicitly nil values.[example 5]

The flag argument selects the iterator method returned for that loop. When flag is an explicit nil or omitted, this returns an iterator for number keys. If given any non-nil flag (i.e., false or any value that evaluates true), this returns a method for looping non-numeric keys. Because both sets are mapped at the same time, you may avoid a redundant mapping op in a subsequent loop by passing an explicit nil or false as flagTemplate:--i.e., omitting flag or passing true indicate that re-mapping is desired.

This automatically selects certain tables for "mapless" iteration. Typically, mapless differs from mapped only in that it uses fewer server resources, though, as explained in the next section on mapping, it may "miss" keys in some cases.

Mapping behavior may be modified or extended by ext. To disable mapless iteration for the table, you may pass false as ext. If not nil or false, ext must be a pairs method that takes the table as its only argument and returns a function that may iterate its keys for mapping purposes. Note that re-mapping avoidance via flag does not apply if ext is explicitly given, though a nil ext does not disqualify a table from mapless iteration. Template:Anchor

Mapping process

Tables are mapped in two stages.

The initial stage is a Template:Luaref which inserts integers between Template:Code and Template:Code in the number key map. Because nothing is checked in this step, this may map keys which the numeric map iterator would pair with nil values or with values from the table's Template:Luaref.

The second stage explores the table's keys with an Template:Luaref and Template:Luaref as the default expression-list, or, if ext evaluates true, the expression returned by Template:Code. This ignores keys already mapped in the first stage and checks if any unmapped key is a number before indexing it in the appropriate map group. Upon completion, if any new number key were found in the second stage, this runs the numeric map through Template:Luaref. No order is imposed on the non-numeric map.

Alternatively, a table may qualify for "mapless" iteration if Template:Luaref is not nil, and Template:Luaref returns nil. If either flag or ext are not nil, or if the table was previously mapped, such permanently disqualifies a table for mapless processing.

As a side note, if mapless numeric iteration occurs, this returns Template:Code. In other words, you may use Template:Luaref to confirm that the table qualifies for mapless iteration when it has a third explicit return (for debugging).



One of four functions may be provided in the Template:Luaref returned by this pairs method, depending on which group of keys (numeric or non-numeric) and which iteration process (map-based or mapless) is indicated.

When key is nil or unspecified, map iterators will return the key object referenced by the first index of the relevant map along with the value it indexes. If passed the first mapped key, these iterators then return the second index mapped, which if passed in turn may retrieve the third and so on until the last mapped key has been served.

For numeric iteration, the mapless method returns Template:Code when key is unspecified. If a key is given, it returns Template:Code unless key is greater or equal to the length of the table, upon which it returns nil. For non-numeric keys, the mapless "iterator" is actually a no-op (empty) function which takes nothing, does nothing, and returns nothingTemplate:--provided only to prevent an error when the for loop expects a function.

As mentioned (using different words), key-value pairs are served independently of whether or not Template:Code exists and retrieved without using Template:Luaref.

For example, take a look at table x as declared in the following statement: Template:Code. Table x has a length equal to 8. With ipairs, the for loop stops after the first pair. In contrast, this module's __pairs method will loop all 8 keys declaredTemplate:--i.e., (1, 1), (2, nil), ... (7, nil), (8, 8). That said, this only iterates two keys if table x were declared as Template:Code instead even though such is indistinguishable to Finally, the loop would continue to include any keys set to nil after the mapping process.</ref>

You may assign these iterators to a local variable to use them directly. If an unmapped table is given to a map iterator, it will forward the table to this pairs method for immediate mapping. Though no map table is produced for the mapless iteration, the pairs method does cache the length of the table at a map reference, which the iterator compares against key to determine when to stop. Unlike the map methods, the mapless iterator does not call the pairs method when such has been bypassed and instead compares key to the value returned by the Template:Luaref, which may be unstable if the loop includes code that sets or unsets indicies within the table. Also, the mapless method will throw an error if given a table that has been mapped (when it attempts to compare key to a map object).



Tips and style recommendations

  • If joining Buffer with a string immediately after :_'text', place a space between 'string' and the separator and use double/single quote marks to . (i.e. :_'text' " " instead of :_'text'Template:` ' or :_'text'(' '))
  • Saving Module:Buffer locally, e.g. local Buffer = Template:Luaref, though fine, is often unnecessary since all Buffer objects can create new buffers via Template:Luaself.

For Template:Luaself

  • Treat Template:Code as though it were a Template:Code op. Wrapping strings with unnecessary Template:Code is akin to Template:Code.
  • To insert an empty string as a placeholder for a separator without setting Template:Code, pass a table containing only an empty string, like so: Template:Luaself.
  • Raw appending a non-table is pointless since no other Scribunto type can tostring differently afterwards. However, this developer believes you are smart enough that Template:Code is a waste of server resources. (Such checks are why Template:Luaref takes twice as much time to append a list of strings as Template:Luaself despite their near-identical roles in an Element-Stream-Buffer).

For Template:Luaself and Template:Luaself

  • Something like Template:Code will use variable A as the parent's separator, or B instead if A is nil, or C if both A and B are nil.

For Template:Luaself

  • Appending values in multiple locations is one of the primary reasons why the nanKeys argument exists. While passing a boolean directly will cause an error, you can do something like...
versus: Template:Code.

For Template:Luaself

  • If the table reference passed as Template:Code was appended raw in multiple positions, this is akin to performing Template:Luaself at all positions simultaneously. (May be easier than trying to come up with a Template:Luaref pattern)
  • Inserting a named empty table is raw as a placeholder to be populated later via this function may be easier than calculating pos argument of Template:Luaself.



Non-literal interpretations of the source code (that is, more opinion than fact) are provided here to offer additional clarity. Overly technical details may be found here as well when including such caveats appears more likely to confuse than help those advanced-but-not-quite-fluent in Lua.

See also

  • Module:Escape, a lightweight metamodule for customized string character escaping

Cite error: <ref> tags exist for a group named "note", but no corresponding <references group="note"/> tag was found
Cite error: <ref> tags exist for a group named "example", but no corresponding <references group="example"/> tag was found

This Module was written by Alexander Zhikun He, also known as, User:Codehydro on the English Wikipedia

All methods were developed independently and any resemblance to other string buffer libraries would be coincidental.
Furthermore, many methods will not work when compiled by standard Lua libraries as they depend on behaviors unique to
the MediaMiki Scribunto mod, which, for example, has a getmetatable() method that always returns nil on non-tables.

Source code comments may be thin at some points because they are intended to be supplemented by the documentation page:

Licensed under Creative Commons Attribution-ShareAlike 3.0 Unported License
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
local noOp, MBpairs = function()end 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 = {__mode = 'k'}--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
	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
			if #ti~=n then
				for k = 1, #ti do tn[ti[k]] = k + 1 end--somewhat wasteful, but trying to avoid overwriting can be even more expensive
			for k = 1, #to do tn[to[k]] = k + 1 end
		return iter and pIter or oMap[t] and pOther or noOp, t--noOp for mapless
local 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 = {__mode='kv', __call=function(t,k,v)t[k]=v return k end}--shared meta for Buffer parent property, raw mode, and specialized functions
	parent, rawkey, spec = setmetatable({}, mkv), setmetatable({}, mkv), setmetatable({}, mkv)--shared meta less memory

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
				return self
			init, stream, _stream = noOp, {
				__call = function(t, v) v = v and Valid(v) return v and table.insert(t, v) or t end,--last_concat cleared before entering stream mode
				__index = function(t, i) return i=='each' and each or MB.__index(t, i) and setmetatable(t, MB)[i] end,--no table look up minimizes resources to retrieve the only stream function
				__tostring = function(t) return setmetatable(t, MB)() end
			} for k, v in next, MB do stream[k] = stream[k] or v end
			setmetatable(stream, getmetatable(MB))
		function _stream(self, ...) self.last_concat = init() return setmetatable(self, stream):each(...) 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
	local function MBselect(n, ...)--helper for :_out and :_str
		local n, seps = n - 1, {select(2, ...)}
		if type(seps[n])=='table' then 
			if buffHTML and rawget(seps[n], buffHTML) then return ... end
			setmetatable(seps, {__index = setmetatable(seps[n], {__index = function(t) return rawget(t, 1) end})})[n] = nil
		return ..., seps
	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
					return tag
				for k, v in next, {[mw] = mwHTMLmeta,
					__call = function(h, v) return MBmix(spec[h.nodes] and h.nodes or spec(setmetatable(parent(h.nodes, h), MB), Element), v) end,
					__concat = false,--false means take from MB
					__eq = false
				} do buffHTML[k] = v or MB[k] end
			local nonSelf, BHi = {tag=true,done=true,allDone=true}, buffHTML.__index do local g
				g = {__index = function(t, i)
					if gfuncs and gfuncs[i] then g.__index, gfuncs = gfuncs return g.__index[i] end
				setmetatable(nonSelf, g)
				setmetatable(BHi, g)
			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
			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', {MB.__call(nodes, sep)})), nodes end
					return join or tostring(HTML)
				for k, v in next, {
					getParent = function(HTML, ...) lastHTML = HTML return MBi.getParent(HTML:allDone(), ...) end,--return to Buffer that created the HTML tree
					getBuffer = function(HTML, ...) lastHTML = HTML return isMBfunc(lastBuffer, ...) end,--return to last used
					killParent = function(HTML, ...) MBi.killParent(HTML:allDone(), ...) return HTML end,
					_out = function(HTML, ...)
						if ...==0 then MBi._out(HTML.nodes, ...) return HTML end
						lastHTML, HTML = HTML, HTML:allDone()
						local n, ops, seps = select('#', ...)
						if n > 1 then
							local ops, seps = MBselect(n, ...)
							return parent[HTML]:_in(joinNode(HTML, rawget(seps, 0))):_out(ops, rawset(seps, buffHTML, true))
						return parent[HTML]:_(joinNode(HTML, ...))
					_str = function(HTML, ...)--does not set lastHTML
						if ...==0 then return joinNode(HTML, select(2, ...)) end--passing 0 strings without calling allDone()
						local HTML, n = HTML:allDone(), select('#', ...)
						if n > 1 then
							local ops, seps = MBselect(n, ...)
							return parent[HTML]:_in(joinNode(HTML, rawget(seps, 1))):_str(ops, rawset(seps, buffHTML, true))
						return joinNode(HTML, ...)
					_parent = function(HTML, ...) table.insert(HTML.nodes, parent[HTML:allDone()]:_str(...)) return HTML end
				} do BHi[k] = v end
			do local htmlArg, skip, outFuncs = {parent=true,selfClosing=true,tagName=true}, {}
				do local out local function func(nodes, ...) return out(parent[nodes], ...) end
					outFuncs = setmetatable({
						tag = function(nodes, ...) return parent(setmetatable(init(nodes, ...), buffHTML), parent[nodes]) end,
						done = function(b, ops)
							b = parent[b] 
							while b.parent and ops~=0 do b, ops = b.parent, ops and ops - 1 or 0 end
							return b
					}, {__index = function(nodes, i)
						if rawget(BHi, i) then out = BHi[i] return func end--rawget to exclude globals
				Element = {
					_add = function(nodes, t)
						for k, v in MBpairs(t), t, skip[t] do (v~=true and MBmix or noOp)(nodes, v) end
						local HTML = parent[nodes] 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 type(v)=='table' then
											skip[v], k = 1, rawset(create(Valid(v[1])), 'parent', HTML)
											Element._add(spec(parent(k.nodes, k, table.insert(nodes, k)), Element), v)
											if k.selfClosing then k.nodes = nil else spec[k.nodes], parent[k.nodes] = nil end--free memory/reduce clutter; parent ref will auto-unset when k.nodes is nil
											if not k.tagName then k.styles, k.attributes = nil end
										else table.insert(nodes, create(v)) end
									elseif mwFunc[k] then
										if k=='done' and tonumber(v)~=v and v[1] and tonumber(v[1])==v[1] then skip[v] = 1 end
										MBmix(outFuncs[k](nodes, skip[v] and v[1]).nodes, v)
									elseif v[1] or v[2] then
										k = MBi[k](nodes, unpack(v, 1, rawset(skip, v, k=='_B' and 1 or 2)[v]))
										Element._add(getmetatable(k) and rawget(k, 'nodes') or k, v)--if k is not a table, then v should not contain any extra keys or this may error.
									else MBi[k](nodes, v) end--k probably == '_G' or '_R'
								elseif mwFunc[k] then
									if type(v)~='table' or rawget(v, 'nodes') then mwFunc[k](HTML, v)
										local css = k=='css'
										for x, y in MBpairs(v, true) do (y and y~=true and mwFunc[k] or noOp)(HTML, css and x:gsub('_', '-') or x, y) end--iterate non-numbers first
										for _, y in MBpairs(v, nil) do (y and y~=true and mwFunc[k] or noOp)(HTML, y) end--don't bother with gsub since text must be quoted anyhow
								elseif rawget(Element, k) or rawget(MBi, k) then
									if tonumber(v)==v or v[1]==nil or getmetatable(v) then (Element[k] or MBi[k])(nodes, v)--v is probably string-able object, or a table to be handled by :_all
									else (Element[k] or MBi[k])(nodes, unpack(v, 1, table.maxn(v))) end--v is definately a table
								else mwFunc.css(HTML, k:gsub('_', '-', 1), tostring(v)) end--oddly enough, :_add clocked its fastest runtime after adding auto-gsub as a feature
								skip[v] = nil
						return nodes
				local tempMeta = {mode='v', copy={styles=true,attributes=true}}
				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
			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
				return lastHTML
			function MBi:_html(...) return MBi._(self, lastHTML, select(spec[self]==Element and select('#', ...)==0 and 1 or 2, true, ...)) end
			return init(...)
		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
	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 = {__tostring = function(a, b) return a.a[0] and pick(b, a.a.string and string.char(a.a[0]) or a.a.table and a.a[1][a.a[0]] or a.a[0]) end}, {
				__index = {c = 1},
				__tostring = function(t) return t:_build() end,
				table = function(a, b) local i = next(a[1], a[0]) or a[0]==#a[1] and next(a[1]) return pick(b, rawset(a.a or a, 0, i)[1][i]) end,--change rate (a.c) ignored since users control the table's contents
				number = function(a, b) return pick(b, c(a, true)) end,
				string = function(a, b) return pick(b, string.char(c(a))) end
			}, {__index = function(a, i) return a.a[i] end, __tostring = function(a, b) return (rawget(a, 0) and a[0]==tostring(a[0]) and rawset(a, 0, a[0]:byte()) or a).a._build(a, b) end}
			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
			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({a = prev, _build = meta.__tostring, c = c}, meta)
				elseif v==nil then--no-op
				elseif c then shift(prev, c)--v == false
				else prev:_build() end
			init, noCache = function(v, c) prev = setmetatable({v, c = c, _build = build[type(v)] or v, [type(v)] = true, alt = {}}, build) return prev end, true
			return init(...)
		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
		function _var(self, ...)
			local obj if ... and ...~=true then obj = init(...)
			elseif prev then
				if ...~=false then obj = rebuild(...)
				else rebuild(...) end
			return obj and MBi._(self, obj, nil, true) or self
	local lib; MBi = setmetatable({stream = _stream,
		_inHTML = _inHTML,
		_var = _var,
		_ = function(self, v, ...)
			local at, raw = select(select('#', ...)==1 and ...==true and 1 or 2, nil, ...)
			if raw then rawkey[self] = math.huge else v = Valid(v) end
			if v or raw then
				if at or rawkey[self] then raw = #self end--if length increases by more than one after table.insert, then set rawkey[self] = math.huge; rawkey[self] may be equal to a previous 'at'
				at, self.last_concat = at and (tonumber(at)~=at and raw + at or at)
				table.insert(self, select(at and 1 or 2, at, v))
				if at and at < 0 or raw and #self - raw > 1 then rawkey[self] = math.huge elseif at and #self==raw then rawkey[self] = rawkey[self] and math.max(rawkey[self], at) or at end
			end--above line looks bizarre because one table.insert op may make length jump from 0 to 8: local wtf={[2]=2,[4]=4,[8]=8}mw.log(#wtf,table.insert(wtf,1),#wtf)
			return self
		_nil = function(self, at, ...)
			if ...~=true and ...~=false then--faster than type(...) ~= 'boolean'
				if not at or at=='0' then
					self[#self] = ... if ... then rawkey[self] = math.huge end
					local n, v = tonumber(at), ...
					if n~=at then 
						if n then n = #self + at
						elseif at~=true and select('#', ...)==0 then v, n = at, #self end
					if n then 
						if v==nil and n > 0 then table.remove(self, n)
						else self[math.floor(n)], rawkey[self] = v, math.huge end--floor position for consistency with Table library
				self.last_concat = nil
			return self
		_all = function(self, t, valKey)
			for k, v in MBpairs(t) do MBmix(self, v, valKey) end
			for k, v in valKey and MBpairs(t, false) or noOp, t do
				if tonumber(v) then MBi._(self, k, v)--self not always a buffer
				elseif rawget(MBi, k) and v and v~=true then
					if v[1]==nil or getmetatable(v) then MBi[k](self, v)
					else MBi[k](self, unpack(v, 1, table.maxn(v))) end
			return self
		_str = function(t, ...)
			local n = select('#', ...)
			if n > 1 then
				local k, ops, seps, r = 2, MBselect(n, ...)
				r = MB(t(seps[1]))
				while parent[t] and ops > 1 and r:_(parent[t](seps[k]), 1) do t, k, ops = parent[t], k + 1, ops - 1 end
				return table.concat(r, seps[k] or nil)
			return MB.__call(t, ...)
		_in = function (self, ...) return parent(MB(...), self) end,
		_out = function(t, ...)
			if ...==0 then return parent(t, parent[t], MBi._cc(t, t, MB.__call(t, (select(2, ...))), getmetatable(t))) end--love how :_cc needed nothing new to implement this *self pat on back*
			local n = select('#', ...)
			if n > 1 then
				local k, ops, seps = 1, MBselect(n, ...)
				while parent[t] and ops > 0 do t, k, ops = parent[t]:_(t(seps[k])), k + 1, ops - 1 end
			elseif parent[t] then return parent[t]:_(t(...)) end
			return t
		_cc = function(self, clear, copy, meta)
			if clear then
				if rawequal(clear, copy) then return self, spec[MBi._cc] and setmetatable(spec[MBi._cc], MB)--rawequal to avoid re-string via __eq in case both are different Buffer objects
				elseif copy==true then copy = self end
				if clear~=0 then
					assert(type(clear)=='table', debug.traceback('Buffer:_cc can only "clear" tables. Did you forget to call with a colon?', 2))--errors can be hard to trace without this
					for k in self and next or noOp, clear do rawset(clear, k, nil) end
				else return MBi._cc(false, {unpack(copy)}, copy) end--copy length w/o empty strings; recursion to avoid self = false causing garbage collection (non-weak child may exist)
				if self==false or copy and type(copy)=='table' then--self==false means copy is a table (saves a type op for recursive calls)
					meta = meta or getmetatable(copy)
					if self and #copy > 1 then--preserves length with empty strings; developed from studying		
						local n, null, i, e = #copy, {}, math.ldexp(2, select(2, math.frexp(#copy)) - 2)
						e, spec[MBi._cc], parent[null] = i - 1, null, clear
						for k = 1, e do table.insert(clear, false) end
						while i<=n do table.insert(clear, i, '') i, null[i] = i + math.ldexp(2, select(2, math.frexp(n - i)) - 2), '' end
						for k = 1, e do rawset(clear, k, nil) end
					for k, v in next, copy do rawset(clear, k, type(v)=='table' and MBi._cc(false, 0, v) or v) end
				elseif copy then rawset(clear, 1, (Valid(copy))) end
				rawkey[setmetatable(clear, meta)], parent[clear] = rawkey[copy], parent[copy]
			return self and rawset(self, 'last_concat', nil) or clear
		_parent = function(self, ...) return parent[self] and MBi._(self, parent[self]:_str(...)) or self end,
		getParent = function(self, ...) return isMBfunc(parent[self] or parent[parent(self, setmetatable({}, MB))], ...) end,
		killParent = function(self, ...) return parent[self] and isMBfunc(parent[self], ...) and parent(self) or self end,
		_build = function(self, t) table.insert(t, self()) end,--for compatibility with mw.html:node()
		last_concat = false--prevent library check
	}, {__index = function(t, i)--import string, mw.text, and mw.ustring libraries on an as-needed basis
		local func = string[i] or mw.text[i] or mw.ustring[i] or type(i)=='string' and mw.ustring[i:match'^u(.+)'] if func then
			lib	= lib or function (s, f, ...)
				if parent[s] and next(s)==nil then return s:_((f(tostring(parent[Element and (spec[s]==Element and s:allDone() or spec[parent[s]]==Element and parent[s]) or s]), ...))) end
				return f(tostring(s), ...)--not using ternary/logical operators here to allow multiple return values
			return rawset(t, i, i:match'^u?gsub' and function(self, p, r, ...)return lib(self, func, p, r or '', ...)end--Why are ugsub/gsub special? because empty strings are against my religion!
				or function(self, ...)return lib(self, func, ...)end)[i]

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({__index = function(t, i) return spec[t] and spec[t][i] or MBi[i] end,
	__call = function(t, ...)
		local rawsep, sep, i, j, raw = noCache and rawkey[...] and ..., ...
		if i or j or rawsep or Valid(sep) then
			raw, sep, i, j = rawkey[spec[t]] or rawkey[t], rawsep or Valid(sep), i and (i~=tonumber(i) and i + #t or i), j and (j~=tonumber(j) and j + #t or j)
			if rawsep or raw and (raw>=(j or #t) or i < 1) then
				raw, i, j = {}, i and math.floor(i), j and math.floor(j)--floor for consistency with table.concat(t, sep, i, j), which ignores decimals, t.last_concat = t.last_concat--temporarily unset last_concat to prevent disqualification from mapless iteration
				for k, v in MBpairs(t) do
					if raw[1] or not i or k>=i then if j and k > j then break end
						if raw.s then raw.s = table.insert(raw, tostring(sep)) end--if sep contains v and v is a Buffer-variable, sep must be strung before v
						k = Valid(v) if k then
							raw.s = rawsep or sep and raw[1] and table.insert(raw, sep)
							table.insert(raw, k)
				if rawsep and not raw.s then raw[#raw] = unbuild(sep) end--unbuild rawsep if final index in t was invalid
				t.last_concat = return table.concat(raw)
			return table.concat(t, sep, i and math.max(i, 1), j and math.min(j, #t))
		return MB.__tostring(t)
	__tostring = function(t)
		if t.last_concat then return t.last_concat end
		local r = rawkey[spec[t]] or rawkey[t]
		r = table.concat(r and r>=#t and MBi._all({}, t) or t)
		return (noCache or rawset(t, 'last_concat', r)) and r
	__concat = function(a, b)
		if buffHTML then
			for k = 1, 2 do local v = select(k, a, b)--faster than for k, v in pairs{a, b} do
				if v and spec[v] and spec[v]==Element then
					if parent[v].selfClosing then
						if rawequal(a, b) then return (not noCache or parent[v].tagName) and v:_str(0):rep(2) or v:_str(0)..v:_str(0) end--rawequal avoids premature tostring of Buffer:_var objects;
						b, a = select(k, b, parent[v], a)
					else local temp = Element .. v --helper method; returns a mirror of parent[v]
						MBmix(MBmix(parent(temp.nodes, temp), a), k==1 and spec[b]==Element and parent[b] or b)
						return buffHTML.__tostring(setmetatable(temp, {__index=parent[v], __mode='v'}))--switch from tempMeta to avoid MBi._cc op of styles/attributes
		return table.concat(MBmix(MBmix({}, a), b))
	__pairs = MBpairs,
	__ipairs = MBpairs,
	__eq = function(a, b) return tostring(a)==tostring(b) end--avoid a==b in this module; use rawequal(a,b) when they may be different Buffers (premature tostring waste ops and is bad for Buffer:_var)
}, {__tostring = function()return''end,
	__call = function(self, ...) MB = MB or self
		if new_G then if ... and _G and ...==_G then new_G = ... end
		elseif ... and (...==_G or type(...)=='table' and (...)._G==...) then
			local Nil, mG = {}, (...):getmetatable() or (...):setmetatable{}:getmetatable()
			new_G, _G, gfuncs = ..., ..., {--gfuncs stored for Buffer:_inHTML; new_G is a is a Module:Buffer local declared just before the final return statement.
				_G = function(self, i, ...)
					local X, save = rawget(new_G, i), select('#', ...)==0 and self or ...
					if i and i~=true and not (X and save and rawequal(X, save)) and rawset(new_G, i, save) and (X~=nil or save==nil and new_G[i]~=nil) then--rawequal in case X is another buffer
						local mG = getmetatable(new_G) or {__call=mG.__call}
						if mG.__index then pcall(rawset, mG.__index, i, X)
						else mG.__index = setmetatable(new_G, mG) and {[i] = X} end
					return self, ...--avoiding __eq with rawequal(self,save) is overkill since buffers can self-save without being passed as save
				_R = function(self, i, v, m)
					if i~='new_G' then if i and i~=true then rawset(new_G, i , v) end
					elseif not v or v==true or v._G~=_G then new_G = setmetatable(v~=true and v or {}, {__call = mG.__call, __index = v~=true and m~=true and (m or new_G) or nil})
					else new_G, (not m and (m~=nil or v==new_G) and Nil or getmetatable(v)).__index = v, m~=true and (m or new_G) or nil end--setting Nil.__index is noOp
					return self
				_2 = function(self, ...)
					if new_G[...]~=nil then return new_G[...] end--higher priority so Buffer:_G('new_G', ...) can prevent an overwrite
					if ...=='new_G' then return rawset((select('#', ...)~=1 and MBi._R(new_G, ...) or new_G), '_G', _G) end
					return select(select('#', ...)==1 and 1 or 2, self:_G(...))--return only one value; 'return select(2, self:_G(...)) or self' doesn't work for returning nil
				_B = function(self, v) return v or v==nil and Nil end
			} for k, v in next, gfuncs do MBi[k] = v end 
			setmetatable(Nil,{__concat=MB.__concat,__newindex=noOp,__call=noOp,__tostring=noOp,__metatable=MB,__index=setmetatable({_B=MBi._B,_=function()return Nil end,last_concat=''},
				{__index=function(t,i)return (MBi[i] or i and not tonumber(i)) and t._ or nil end})})
			function mG.__call(G, k, ...) return (k._G or G.type(k)=='table') and ('#', ...)~=1 and G.rawset(k, ...) or G:rawset(..., k) and k) or G:rawset(k, (...)) and ... end
		local new = setmetatable({}, self)
		if ... and (...)==new_G then return select(2, ...) and MBmix(new:_G((select(2, ...))), select(3, ...)) or new end
		return ... and MBi._(new, ...) or new
	__index = function(t, i)
		MB = MB or t return MBi[i] and function(...) return MBi[i](setmetatable({}, t), select(...==t and 2 or 1,...)) end