Module:Buffer/doc: Difference between revisions

Content deleted Content added
save
save... note: Buffer:stream is not yet live
Line 1:
{{Module rating |release<!-- Values: pre-alpha • alpha • beta • release • protected -- If a rating not needed/relevant, delete this template call -->}}
<!-- Please place categories where indicated at the bottom of this page and interwikis at Wikidata (see [[Wikipedia:Wikidata]]) -->
 
== Usage ==
This module was originally developed to optimize string concatenation in [[Module:Asbox]] but can be used in any module.
 
Line 8:
Buffers can also be appended to {{luaref|HTML library|mw.html}} objects via {{luaref|mw.html:node}} (though not mw.html:wikitext because of type checking). (See also [[#usage with string/mw.text libraries]])
 
==Basic usage==
{{anchor|module}}
{{anchor|module|initialize}}
===require('Module:Buffer')===
===require'Module:Buffer'===
{{luaself|Buffer|\|require('Module:***')||subpage= |args=...|args2=_G, name, save, ...}}
 
CallCreates thea modulenew asModule:Buffer aobject functionwhen toit create(the amodule) newis Module:Buffercalled objectas a function{{--}}i.e., there is no 'main'.
 
SomeInitialize functionsthe aremodule availablewith onlyyour if Module:Buffer is initialized with the"local" global variable {{luaref|_G}} prior to the creation ofcreating any Buffer objects (seeto enable [[#advancedGlobal functions|global functions]] for more detail). If initialized withpassed _G then anythe next two {{luaref|varargs|plain=y}} will be passedpass to {{luaref|self=Buffer/docluaself|:_G}} and any extra will pass to {{luaself|args=\:_}}. If initialized without _G then anyall varargs will by passedpass to {{luaref|self=Buffer/docluaself|:_|args=...}}.
 
You may also use anymost Buffer object functionfunctions directly on the module{{--}}i.e <code style='white-space:nowrap'>require('Module:Buffer'):''function''{...}</code> is equivalent to <code style='white-space:nowrap'>require('Module:Buffer')():''function''{...}</code>. The self operator, {{code|:}}, and {{code|.}} are interchangable when used on the Module, but is required for all other interactions with Buffer objects (other than {{luaself|.last_concat}}).
 
====Buffer====
 
{{luaself|\|args=sep, i, j}}{{anchor|call Buffer object}}
:''See also {{luaself|:_str}} for advanced string conversion.
 
AGet Buffer as type {{luaref|string||y}} by performing a function call on athe ''Buffer ''object'' (as opposed to [[#require('Module:Buffer')|callinga call on the ''Module'']]). Calling a Buffer is basically shorthand for {{luaref|table.concat|args=Buffer, ...}}, or, with no args, {{luaref|tostring|args=Buffer}}.
 
However, if your Buffer contains values inserted via {{luaref[[#raw|self=Buffer/doc|:_}} that are either raw]] (see below section)objects or are out-of-sequence (i.e. keys of type 'number' ignored by {{luaref|ipairs}})values, then the return string returned would be fromthe result of {{luarefluaself|self=Buffer/doc|:_all|''empty-buffer'':_all|args=Buffer}}{{code| )( ... )}} instead.<ref group=note name=raw />
 
{{anchor|Buffer.last_concat}}
When called without a separator or used as a string, the concatenated result may be retrieved via {{code|Buffer.last_concat}}. Future calls or tostring operations on the Buffer will return Buffer.last_concat until the Buffer is modified. (See next section for details on caching behavior.)
{| style='border-collapse:collapse'
|{{TOC tab|'''Buffer.last_concat<small><br/>Caching behavior and how to 'purge'</small>'''|Caching behavior and how to 'purge'|depth=4}}
|}
When strung without a separator, the result may be retrieved via {{code|Buffer.last_concat}}. Future tostring operations on the Buffer will return Buffer.last_concat until it is modified. You may purge the cache by setting this key to nil, by appending a [[#valid|valid value]] and immediately removing it: <code>{{luaself|:_|args=(0)|plain=y}}{{luaself|Buffer|:_nil|:_nil|y|args=()}}</code>, as well as by passing an empty table to {{luaself|:_c|args={} }}.
 
====Buffer:_====
 
{{luaself|:_|args='string'|args2=value, pos, raw|args3=value, raw}}
 
Appends a value to the Buffer. In rough terms, <{{code>|lang=lua|Buffer:_'string1':_'string2'</code>}} is the same as <{{code>|lang=lua|Buffer {{=}} Buffer..'string1'..'string2'</code>}}. (IfIt itmay helps,help to imagine {{code|:_}} as a {{code|..}} that has stood up and is now casting a shadow).)
 
{{anchor|valid}}<u>No|no-op values</u><br />}}
If passed an ''invalid'' {{code|value}} thatlisted is any of the followingbelow, thenthis {{code|:_}} will beis a no-op:
* {{luaref|nilboolean||plain=y}}
* {{luaref|booleannil||plain=y}}
* an empty {{luaref|string|plain=|y}}
* a {{luaref|table|plain=|y}} without a {{luaref|Metatables|__tostring}} metamethod}} and which <code>''table''[1]</code> is nil or false.
 
TablesA lackingtable awith __stringno metamethod__tostring arewill convertedpass to string viathrough {{luaref|table.concat}} before insertion. An {{luaref|error|plain=y}} may be thrown if the table passed is one that would cause table.concat to error. (Use {{luaself|:_all}} instead.)
 
For all other data values''value'', the result of {{luaref|tostring|args=value}} would be inserted so long as it is not an empty string.
 
{{anchor|raw}}
Set {{code|raw}} to true to force ''value'' in Buffer without tostring coercion, including [[#no-op|invalid]] values.<ref group=note name=raw>Setting a Buffer to ''raw'' incurs [[#performance|performance]] penalty for all future tostring ops as it must re-validate each indexed value through {{luaself|:_all}} to a new table before passing that to table.concat (vs. passing itself directly). That said, re-stringing a raw Buffer is still usually several times faster than using the {{luaref|Concatenation operator|..}} op to join an equivalent number of strings. (See [[#Tips]] for ways to avoid using raw)</ref>
 
When passed {{code|pos}} of type {{luaref|number|plain=y}}, the argument is identical to ''pos'' for {{luaref|table.insert|args=table, pos, value}}. In fact, assuming a [[#valid|valid value]], {{luaself|:_|args='string', 1}} is exactly the same as {{luaref|table.insert|args=Buffer, 1, 'string'}}.
Set {{code|raw}} to true to insert without tostring coercion (e.g., an unfinished mw.html or Buffer object). Note however that raw will cause all future conversions of the Buffer to be handled by {{luaself|:_all}} instead of table.concat, incurring a [[#performance|performance]] penalty that may be undesirable for highly transcluded modules. (See [[#Tips]] for ways to avoid using raw)
 
Unconventionally, a ''pos'' of type {{luaref|string|plain=y}} is treated as relative to length; that is, {{luaself|:_|args='string', '-1'}} is equivalent to {{luaself|:_|args='string', #Buffer - 1}} (obviating the need to set a {{luaref|Local variable declarations|local|y}} Buffer to use the {{luaref|Length operator|length operator|y}}). If given only two (non-self) arguments with the second being a boolean, then the second is read as ''raw'' instead.
 
===Buffer:_nil===
When passed {{code|pos}} of type {{luaref|number|plain=y}}, the argument is the same as for {{luaref|table.insert|args=table, '''pos''', value}}. (In fact, assuming a [[#valid]] value, {{luaself|:_|args=''value'', 1}} is exactly the same as {{luaref|table.insert|args=Buffer, 1, ''value''}}.)
 
{{luaself|:_nil|args=pos, replacement}}
The treatment of {{code|pos}} of type {{luaref|string|plain=y}} is unconventional in that the length of the Buffer is added to {{code|pos}}. In other words, {{luaself|:_|args='string', '-1'}} is the same as {{luaself|:_|args='string', #Buffer - 1}} (obviating the need to store Buffer as a local variable just to use the {{luaref|Length operator|length operator|plain=y}}).
 
Removes the value buffered at {{code|pos}}. As with {{luaself|:_}}, a string ''pos'' string is treated as {{code|lang=lua|#Buffer + pos}}.
====Buffer:_all====
 
If {{code|replacement}} is provided, then this will replace the value at the ''pos'' index as long as ''replacement'' is not a boolean, in which case, this is a no-op.
{{luaself|:_all|args={ ''value'', ... }|args2={ ''value'', ''value'' {{=}} ''pos'', ... }, ''value-key''}}
 
When ''replacement'' is nil, the op is simply {{luaref|table.remove|args=Buffer, pos}} with string ''pos'' relative to length. Note however there is no further type checking on replacement, so, if nil nor boolean, then Buffer will be set to [[#raw|raw]].
====Buffer:_in====
Saving Module:Buffer locally, e.g. <code>local Buffer = {{luaref|require|args='Module:Buffer'|plain=y}}</code>, though fine, is often unnecessary since all Buffer objects can create new buffers via {{luaref|self=Buffer/doc|:_in}}.
 
Pos cannot be omitted if replacement is passed, though a ''pos'' that is nil will be treated as <code>{{#tag:syntaxhighlight|#Buffer|lang=lua|enclose='none'}}</code>.
====Buffer:_inHTML====
 
===advanced functionsBuffer:_all===
 
{{luaself|:_all|args={ value, ... }|args2={ value, value {{=}} pos, ... }, nanKeys}}
 
Takes a table {{code|value}}, iterates through all number keys {{luaref|table.sort|in order|plain=yes}}, appending each [[#valid|valid]] value to the end of the Buffer. In contrast to {{luaref|ipairs}}, the iteration starts at the most negative key (down to {{luaref|math.huge|-inf|y}}), continues through any nil keys, until it reaches the most positive index and includes [[Double-precision floating-point format|non-integer number keys]]. (Note: despite more thorough iteration, the runtime of Module:Buffer's iterator is almost statistically indistinguishable from that of ipairs. Details at [[#Performance]] and [[#Using the iterator outside of buffer]].)
 
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 {{luaself|:_}}.
==usage with string/mw.text libraries==
 
{{anchor|nanKeys}}
The iteration excludes non-number keys unless {{code|nanKeys}} evaluates true. However, keep in mind non-number keys are iterated after number keys in {{luaref|next|no particular|y}} order, though order may be imposed by wrapping each pair in a table indexed at a number key.
 
If the <u>value</u> at the index is either a number or a number string then the ''key'' and ''value'' pairs will be passed as the {{code|value}} and {{code|pos}} argument of {{luaself|:_}}, respectively. Thus, {{luaself|:_all|args=({1,2,3,'... done',[3.5]=variable and 4 or {four='1',zero=1{{))}},true)}} produces the same result as:
==Tips==
{{#tag:syntaxhighlight|Buffer:_(1):_(2):_(3)
* To insert an empty string without setting {{code|raw}} as a placeholder for a {{luaref|table.concat|concat separator}}, use {{luaself|:_|args={<nowiki>''}</nowiki>}} (i.e. a table containing only a empty string.)
if variable then
Buffer:_(4)
else
Buffer:_'four':_('zero',1)
end
Buffer:_'... done'|lang=lua}}
 
If the ''nanKey'' iteration encounters a value that cannot be {{luaref|tonumber|coerced into a number|y}} and which is not boolean, then, if the ''key'' matches the name of a Buffer function, the match will be called. The arguments, if {{code|value[1]|lang=lua}} evaluates true, will be the return of {{luaref|unpack|args=value, 1, table.maxn(value)}}; otherwise, the value is passed as is.<ref group=note>In other words, if the value is a non-number string or a table without [1] set, the value will be passed as the only arg. A function value throws an error message.</ref> For example:
==Example==
For example, take the following snippet from the p.templatepage() function of [[Module:Asbox]].
<pre>
content = buffer(page.text ~= 'Stub' and require('Module:Asbox stubtree').subtree{args = {pagename = page.text}})
:_in'\n== About this template ==\nThis template is used to identify a':_(args.subject):_'stub':_(args.qualifier):_out' '
:_'. It uses {{[[Template:Asbox|asbox]]}}, which is a meta-template designed to ease the process of creating and maintaining stub templates.\n=== Usage ===\nTyping <code>{{'
:_(page.text == 'Stub' and 'stub' or page.text)
:_'}}</code> produces the message shown at the beginning, and adds the article to the following categor'
:_(#stubCats > 1 and 'ies' or 'y')
:_':\n'()
</pre>
 
{{code|1=p(_G,'arg', true):_all({'arg',arg==true and {'==true: ' ,_in={_G, 't', nil, ' awesome'}{{))}}, true):_(t and {t(), t..'r', t..'st'})|lang=lua}}
That snippet is basically equivalent to:
 
<pre>
produces: {{code|1='arg==true: awesome awesomer awesomest'|lang=lua}}
content = page.text ~= 'Stub' and
 
require('Module:Asbox stubtree').subtree{args = {pagename = page.text}}
===Buffer:_in===
or ''
 
content = content .. '\n== About this template ==\nThis template is used to identify a'
{{luaself|:_in|args=...|args2=_G, name, save, ...}}
if args.subject then
 
content = content .. ' ' .. args.subject
Creates and returns a new Buffer object. This does not <u>not</u> append the new Buffer to the parent. (See next section)
end
 
content = content .. ' stub'
More precisely, it re-calls the {{luaself|Buffer|require'Module:Buffer'|Module:Buffer|y}} instance which created the Buffer object with the passed arguments and then adds a reference in the new Buffer that allows it to retrieve its parent.<ref group='note' name='in-dependents'>However the parent will not contain a reference to the child. Calling {{luaself|:getParent}} on the child without first referencing it with either the [[#global functions]] or a local variable will cause the child to become irretrievable. This is intentional as setting a reference would prevent {{luaref|Garbage collection}} on child Buffers that have no futher purpose.</ref> Do not pass _G if the Module:Buffer instance was not initialized with [[#Global functions|global functions]] enabled.
if args.qualifier then
 
content = content .. ' ' .. args.qualifier
===Buffer:_out===
end
 
content = content
{{luaself|:_out|args=sep|args2=outs, sep-list, { default-sep, [out] = sep, ... } }}
.. '. It uses {{[[Template:Asbox|asbox]]}}, which is a meta-template designed to ease the process of creating and maintaining stub templates.\n=== Usage ===\nTyping <code>{{'
 
.. (page.text == 'Stub' and 'stub' or page.text)
{{luaref|table.concat|Joins|y}} Buffer with {{code|sep}} and appends result to the parent Buffer. Returns the parent. If no parent is found, this is a no-op and returns the same Buffer.
.. '}}</code> produces the message shown at the beginning, and adds the article to the following categor'
 
.. (#stubCats > 1 and 'ies' or 'y')
If given more than one (non-self) argument, then the first is read as {{code|outs}}{{--}}the number of :_out() to perform.<ref group=note>The first argument is not type checked. For [[#performance]], it is read as ''outs'' only when there are multiple varargs. In short, use {{luaself|:_outs|args=''N'', nil}} if you desire to append ''N'' generations to their parent with no separator.</ref> Each additional argument in ''{{code|sep-list}}'' is applied as ''sep'' for that :_out operation. That is, the first ''sep'' applies to the current Buffer, the second to its parent Buffer, the third to its grandparent, and so on.
.. ':\n'
 
</pre>
If the last vararg is a table, its first index will be applied as the default key for all nil varargs. The table may immediately follow ''outs'' (i.e. ''sep-list'' may be omitted). If it contains other keys, then the value of key N would be applied as ''sep'' for the Nth :_out() instead of ''default-sep'', making the following two snippets equivalent: {{luaself|:_out|args=4, nil, nil, nil, ' and ', {', '} }} and {{luaself|:_out|args=4, {', ', [4] = ' and '} }}. A false index signifies that ''default-sep'' should not be used for that generation (an empty string will do the same).
 
===Buffer:_str===
 
{{luaself|:_str|args=sep|args2=generations, sep-list, { default-sep, [generation] = sep, ... } }}
 
Joins a Buffer with {{code|sep}} and returns the string. Varargs are handled by the same function as {{luaself|:_out}}, which, if provided, this will create a new temporary Buffer and backtrack the number of {{code|generations}} specified, inserting each ancestor in front of its descendants in the temporary Buffer. The ''sep'' indexed at {{code|generations + 1|lang=lua}} will be used as the joiner for the temporary Buffer (unless the first ancestor is reached before the specified number of generations, in which case it is the index following that of the original generation).
 
Unlike :_out, this does not append the child into the parent. As such, even with the same arguments, it may return a different result than would be obtained from stringing the return of :_out since each parents' ''sep'' is not used to join parent and child.
 
===Buffer:_parent===
 
{{luaself|:_parent|args=outs, sep-list, { default-sep, [out] = sep, ...} }}
 
Similar to {{luaself|:_out}} except, instead of apending the Buffer to its parent, this calls {{luaself|:_str}} on the parent(s) and appends the result.
 
===Buffer:getParent===
 
{{luaself|:getParent|args=...|args2=functionName, ...}}
 
Returns parent Buffer, or, if none exists, sets a newly created Buffer as the 'parent' and returns the adopted parent.<ref group=note>Note, as though it created the child with {{luaself|_in}}, the parent contains no reference to the child. See also: <ref group='note' name='in-dependents'/></ref>
 
===Buffer:_c===
 
{{luaself|:_c|args=clear|args=clear, copy}}
 
Nils all keys of the table referenced by {{code|clear}} and unsets its metatable. If passed an empty table, this simply purges the cache at {{luaself|.last_concat}}.
 
If given a table to {{code|copy}}, it will also duplicate all key-value pairs of ''copy'' into ''clear'', passing any value of type table through {{luaref|mw.clone}}. The former will also acquire the latter's metatable, though, if ''copy'' is an {{luaref|HTML library|mw.html object}}, [[#Buffer:_inHTML|Module:Buffer's augmented version]] would be set instead.
 
If ''copy'' is not a table, then it will be inserted as the first index of the ''clear''ed table so long as ''copy'' is not nil or false.
==Stream mode==
===Buffer:stream===
 
{{luaself|:stream|args='string'|args2={ concat-list, ... }|args3=(var)}}{{anchor|streaming|calling a Buffer:stream}}
 
Switches the Buffer to stream mode. In this mode, the [[#call Buffer object|Buffer call]] operation, instead of returning a string, now acts as a simplified version of {{luaself|:_}}.
 
When streaming, you may append a sequence of strings with nothing in between them (or only {{luaref|Character class|ASCII space chars}} if desired). For example, both A and B will produce identical strings:{{#tag:syntaxhighlight|
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':buffer'and continue to to appended individually':_str' '
 
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 to appended individually':_str' '
 
=mw.dumpObject{A, B, A==B}
table#1 {
"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 appended individually",
"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 appended individually",
true,
}
|lang=lua}}
 
Aside from saving two characters per string, this mode is approximately [[#performance|50 percent faster]] than :_ (which is already much faster than the .. op).
 
Regular Buffer functions are temporarily unavailable in this mode. Other than [[#calling a Buffer:stream|the Buffer:stream itself]], the only useable functions are {{luaself|\:stream:flow}} and {{luaself|\:stream:buffer}}.
 
 
 
==HTML extension==
===Buffer:_inHTML===
 
{{luaself|:_inHTML|args=tagName, args}}
Creates an augmented {{luaref|HTML library|mw.html object}}. The arguments are identical to that of {{luaref|mw.html.create}}.
 
Enhancements are as follows:
* Allows {{code|..}} op to be used on Buffer-mw.html objects directly (no {{luaref|tostring}} needed).
* If initialized, will store tags and wikitext in an Element-Buffer, with which you may use Module:Buffer object functions to append (and remove, etc.) values.
* Element-Buffer objects may use {{luaself|Buffer|Element-Buffer:_add}}, which greatly reduces the code size needed to build an equivalent mw.html object.
 
===HTML-Buffer===
 
{{luaself|Buffer|HTML-Buffer|args='string'|args2={ wikitext, { tag = { tagName, arg = value, wikitext, htmlFunction = args, tag = tagName } }, ..., attr = { name = value } } }}
 
Converts the Buffer-HTML's holding table into an Element-Buffer Appends text or tags
 
==Global functions==
 
 
==Modified {{code|..}} operator==
{{code|Buffer .. value}}<br />
<code>''Buffered-HTML-object'' .. value</code><br />
This is akin to {{luaref|self=Buffer/doc|:_all|'''new-buffer'':_all|args={ Buffer, value} }} or <code>{{luaref|tostring||p|args=Buffer}} .. value</code>. HTML objects created by a Buffer may also be concatenated in this manner.
 
<code>''HTML-Buffer'' .. value</code><br />
 
 
<code>value .. ''Element-Buffer''</code><br />
 
 
==Using 'all' pairs outside of buffer==
 
==String, mw.ustring, and mw.text functions==
 
{{anchor|tips|Tips}}
==Tips and style recommendations==
* If [[#Buffer|joining Buffer]] with a string immediately after <code>:_'<i>text</i>'</code>, place a space between 'string' and the [[#Buffer|separator]] and use double/single quote marks to . (i.e. <code>:_'<i>text</i>' " "</code> instead of <code>:_'<i>text</i>'{{`}} '</code> or <code>:_'<i>text</i>'(' ')</code>)
* Saving Module:Buffer locally, e.g. <code>local Buffer = {{luaref|require|args='Module:Buffer'|plain=y}}</code>, though fine, is often unnecessary since all Buffer objects can create new buffers via {{luaref|self=Buffer/doc|:_in}}.
 
 
'''For {{luaself|:_}}'''
 
* Treat {{code|:_}} as though it were a {{code|..}} op. Wrapping strings with unnecessary {{code|()}} is akin to {{code|( 'string1' ) .. ( 'string2' ) .. ( 'string3' )}}.
* Most uses of {{code|raw}} can be avoided through careful planning with the {{code|pos}} argument. That said, the performance decrease from ''raw'' is unlikely to be significant for modules transcluded on less 100,000 pages. In short, reduction in server load from avoiding ''raw'' may not be worth it if such makes the code harder to maintain.
* To insert an empty string as a placeholder for a [[#Buffer|separator]] without setting {{code|raw}}, pass a table containing only a empty string, like so: {{luaself|:_|args={''} }}.
 
'''For {{luaself|:_all}}'''
 
* Appending values in multiple locations is one of the primary reasons why the [[#nanKeys|nanKeys]] argument exists. While passing a boolean directly will cause an error, you can do something like...
::this:{{sp|3}}{{code|lang=lua|1=Buffer:_all({condition and {_nil={'0', 'replacement'},Front=1,getParent='from child'{{))}}}, true)}}
::versus: {{code|lang=lua|Buffer:_nil('0', condition and 'replacement' or false):_(condition and 'Front', 1):getParent(condition and 'from child'):_B(child)}}.
 
'''For {{luaself|:_c}}'''
 
* If the table reference passed as {{code|clear}} was appended [[#raw|raw]] in multiple positions, this is akin to performing {{luaself|:_nil}} at all positions simultaneously. (May be easier than trying to come up with a {{luaref|string.gsub}} pattern)
* Inserting a [[#Buffer:_G|named]] empty table is raw as a placeholder to be populated later via this function may be easier than calculating ''pos'' argument of {{luaself|:_}}.
 
 
{{anchor|performance}}
==Performance==
 
==Notes==
{{reflist|group=note}}
 
<includeonly>{{#ifeq:{{SUBPAGENAME}}|sandbox | |