diff options
author | Karl Berry <karl@freefriends.org> | 2012-05-27 22:26:24 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2012-05-27 22:26:24 +0000 |
commit | 0029c3d1007a0e692719efde5aa4252004a75ad5 (patch) | |
tree | 5d9b790c1863d6bee5ceaefa1ccfd77dfe78da5c | |
parent | 9ac70a9e937e06c78564f9241963b4918e925302 (diff) |
gates 0.2 (27may12)
git-svn-id: svn://tug.org/texlive/trunk@26675 c570f23f-e606-0410-a88d-b1316a301751
-rwxr-xr-x | Master/texmf-dist/doc/generic/gates/README | 4 | ||||
-rwxr-xr-x | Master/texmf-dist/doc/generic/gates/gates-doc.pdf | bin | 155218 -> 182321 bytes | |||
-rwxr-xr-x | Master/texmf-dist/doc/generic/gates/gates-doc.tex | 9 | ||||
-rwxr-xr-x | Master/texmf-dist/doc/generic/gates/gates-doc.txt | 145 | ||||
-rwxr-xr-x | Master/texmf-dist/doc/generic/gates/gates.tex | 2 | ||||
-rw-r--r-- | Master/texmf-dist/tex/generic/gates/gates.lua | 243 | ||||
-rw-r--r-- | Master/texmf-dist/tex/generic/gates/gates.sty | 4 | ||||
-rw-r--r-- | Master/texmf-dist/tex/generic/gates/t-gates.tex | 4 |
8 files changed, 291 insertions, 120 deletions
diff --git a/Master/texmf-dist/doc/generic/gates/README b/Master/texmf-dist/doc/generic/gates/README index 5af453033ae..baa717a276c 100755 --- a/Master/texmf-dist/doc/generic/gates/README +++ b/Master/texmf-dist/doc/generic/gates/README @@ -2,8 +2,8 @@ This is the README file for the Gates package. Author: Paul Isambert. E-mail: zappathustra AT free DOT fr Comments and suggestions are welcome. -Date: December 2011. -Version: 0.1. +Date: May 2012. +Version: 0.2. Gates helps writing code in a modular fashion: macros are broken into small chunks with names, which can be externally controlled and/or diff --git a/Master/texmf-dist/doc/generic/gates/gates-doc.pdf b/Master/texmf-dist/doc/generic/gates/gates-doc.pdf Binary files differindex 30a7ac42788..f0e81129653 100755 --- a/Master/texmf-dist/doc/generic/gates/gates-doc.pdf +++ b/Master/texmf-dist/doc/generic/gates/gates-doc.pdf diff --git a/Master/texmf-dist/doc/generic/gates/gates-doc.tex b/Master/texmf-dist/doc/generic/gates/gates-doc.tex index 906f9d555d5..943e06f8edd 100755 --- a/Master/texmf-dist/doc/generic/gates/gates-doc.tex +++ b/Master/texmf-dist/doc/generic/gates/gates-doc.tex @@ -1,7 +1,7 @@ % This is the master file producing gates-doc.pdf. The version of the % documentation readable in a text editor is interpreter-doc.txt (input below). % -% Paul Isambert - zappathustra AT free DOT fr - December 2011 +% Paul Isambert - zappathustra AT free DOT fr - May 2012 \input pitex \input interpreter @@ -102,13 +102,14 @@ \def\comarg{\arg{command}\antigobblespace} \def\textarg{\arg{text}} -\setbox0=\hbox{% +\setbox0=\hbox to \hsize{% {\big \color{.8 0 0}{Gates}}% + \hfil \vbox{% \hbox{Paul Isambert} \hbox{\codefont zappathustra@free.fr} - \hbox{Version 0.1} - \hbox{December 2011}}% + \hbox{Version 0.2} + \hbox{May 2012}}% } \ht0=6\baselineskip \dp0=0pt \box0 diff --git a/Master/texmf-dist/doc/generic/gates/gates-doc.txt b/Master/texmf-dist/doc/generic/gates/gates-doc.txt index 9d22ebf6b46..81562d67e51 100755 --- a/Master/texmf-dist/doc/generic/gates/gates-doc.txt +++ b/Master/texmf-dist/doc/generic/gates/gates-doc.txt @@ -186,13 +186,19 @@ in a row to the same l-gate. See {loop in TeX} [see TeXLoop_tag] and {loop in Lua} [see LuaLoop_tag]. -Finally, |loopuntil| is another kind of loop: the gate is repeated as +Fourth, |loopuntil| is another kind of loop: the gate is repeated as long as it is false; also, |loopuntil| is evaluated after the gate, so the latter is executed at least once. If a gate has both |loop| and |loopuntil|, the latter is ignored. See {loop-until in TeX} [see TeXLoopUntil_tag] and {loop-until in Lua} [see LuaLoopUntil_tag]. +Finally, in Lua a gate may also have an |iterator|; to put it simply, +it mimicks a "for" loop; for instance, a gate may be called with a +table as its argument but actually be executed on every entry, if the +|iterator| is the "pairs" function. + See {iterator in Lua} [see LuaIterator_tag] for details. + Actually, a gate doesn't have only one status, one conditional, etc. Rather, it has one global set of conditions, and one local set of conditions for each l-gate where it appears (note that if a gate appears @@ -212,6 +218,7 @@ gate has neither |loop| nor |loopuntil|, the gate is executed once; otherwise, it is repeated as long as |loop| is true or as long as |loopuntil| is false. Note that even if the gate is executed several times, the status and conditional aren't reevaluated, only the loops. +The same is true with |iterator| in Lua. Suppose now the gate occurs in an l-gate. Then the same evaluation happens, this time with the local values for that given l-gate. If the @@ -1444,6 +1451,23 @@ If |autoreturn| is false, the following gate will receive "nil" and and "D"; the first argument isn't restored, even though it is "nil", because it is followed by real argument "X". +One can also uses |autoreturn| to completely disregard whatever a gate +returns; this happens when |autoreturn| is a function instead of a +boolean; the function is passed the original arguments given to the +gate, and what it returns overrides the gate's return values. For +instance, given the same definition of "mygate", if |autoreturn| is: + + gates.autoreturn ("mygate", + function (A, B, C, D) return D, C, B, A end) + +then no matter what "mygate" returns (including what it returns on +several iterations of itself, if any), the following gate will always +receive the original arguments in reverse order. This can be particularly +useful if the gate is controlled by an |iterator|, in which case the +arguments it receives (e.g. the entries of a table) differ from the +original ones (e.g. the table itself), yet you still want to pass the +latter to the following gate. + L-gates automatically returns whatever their last gate returns. Also, unlike the TeX implementation (but in line with the Lua language), when a gate returns outside an l-gate, the returned values can be used, as @@ -1529,6 +1553,89 @@ after the gate is executed, so the execution takes place at least once. If both |loop| and |loopuntil| are set for a gate, the latter is ignored. + -- Iterator -- :LuaIterator_tag + +The |iterator| action is a bit more complex. If set, it is fed the +arguments passed to the gate and should return a function, plus possibly +a state, plus possibly an initial variable; in other words, it should +return whatever fits a "for" loop in Lua. Then the function is called +with the state and variable, and the gate itself is called on whatever +this function returns; the process repeats until the function returns +"nil" as its first argument. As an example: + + gates.mygate = function (key, value) + print("The value of " .. key .. " is " .. value) + end + gates.iterator ("mygate", pairs) + gates.mygate({x = 55, y = 22}) + +will print: + + The value of x is 55 + The value of y is 22 + +Here "pairs" was used to return the function, state and variable mentioned +above; but of course you can make you own function: + + gates.mygate = function (w) + print(w .. " has " .. #w .. " characters.") + end + gates.iterator ("mygate", + function (s) + local t = string.explode(s) + local i = 0 + local function enum () + i = i+1 + return t[i] + end + return enum + end) + gates.mygate("two words") + +And the result is: + + two has 3 characters. + words has 5 characters. + +Here the function registered in |iterator| produces a single function, +without a state, and goes through all the entries of the table created +by splitting the original string. In sum, what |iterator| expects is +what Lua's generic |for| expects, and understanding the latter is +understanding the former. + +An important point to keep in mind when using |iterator| is that there +is a discrepancy between the arguments passed to the gate and the ones +it really processes; in the first example, "mygate" is called with a +table, but it receives two strings. This means that its definition +doesn't match the way it is called; in that respect, unlike |loop| and +associates above, you just can't impose an |iterator| on a aready defined +gate and expect everything to be fine, since its definition probably +won't match the arguments it will now receive. + +Another point is the return values, if any; during the iteration, the +gate's return values are ignored; then the last ones are passed to the +following gate, possibly augmented with some of the original arguments +if |autoreturn| is "true". Note that the original arguments here are +those passed to the gate before the iteration states. This holds if +|autoreturn| is a function too: + + gates.mygate = function (what, ever) + -- Whatever. + end + gates.iterator ("mygate", pairs) + gates.autoreturn ("mygate", + function (t) return t end) + gates.mygate (mytable) + +Here "mygate" will loop on the entries in "mytable" (thanks to "pairs"), +and once the iterations are over, "mytable" is returned. + +Of course, |iterator| can be used with l-gates too; that works the +same: whatever the iterator returns is simply passed to the subgates. +Finally, if a gate has either |loop| or |loopuntil|, |iterator| is +ignored. + + The shorthand notation :LuaShorthand_tag @@ -1538,11 +1645,11 @@ Gates can be created and manipulated by actions, as we've done up to now, but they can also be declared much faster. First, |def| and |list| take tables as their arguments; that is for a good reason: entries indexed with certain keys are equivalent to actions. The keys are -|autoreturn|, |status|, |conditional|, |loop| and |loopuntil| and setting -them when declaring a gate is like globally setting the associated -action (except there is a single action for |status|, which takes a -string as its value: "open", "ajar", "skip" or "close"). Thus the -following defines an m-gate with global status "ajar" and a |loop|: +|autoreturn|, |status|, |conditional|, |loop|, |loopuntil| and |iterator| +and setting them when declaring a gate is like globally setting the +associated action (except there is a single action for |status|, which +takes a string as its value: "open", "ajar", "skip" or "close"). Thus +the following defines an m-gate with global status "ajar" and a |loop|: gates.def {"mygate", status = "ajar", @@ -1697,11 +1804,16 @@ latter case only if an l-gate is also specified, obviously). "ajar". With <l-gate>, sets the local status in <l-gate> to "ajar" for the gates in <gate spec>. -> autoreturn (<gate spec>[, <l-gate>], <boolean>) +> autoreturn (<gate spec>[, <l-gate>], <boolean or function>) Without <l-gate>, sets the global |autoreturn| for the gates in <gate spec>; with <l-gate>, the local |autoreturn| is done. When a - gate's |autoreturn| is set, arguments will be restored if it returns - less than what it was passed. + gate's |autoreturn| is set to "true", arguments will be restored if + it returns less than what it was passed; if |autoreturn| is a + function, it is passed the original arguments passed to the gates + and what it returns will be the ultimate return values of the gate. + In the latter case, the gate's conditions are ignored, i.e. the + arguments passed to |autoreturn| are the ones fed to the gate before + it loops or iterates (if it does that). > close (<gate spec>[, <l-gate>]) Without <l-gate>, sets the global status for the gates in <gate spec> to @@ -1751,6 +1863,21 @@ latter case only if an l-gate is also specified, obviously). The family associated with a table created with |new|. This is a string, not a function. +> iterator (<gate spec>[, <l-gate>], <function>) + Without <l-gate>, sets the global iterator for the gates in <gate spec> + to <function>. With <l-gate>, sets the local iterator in <l-gate> to + <function> for the gates in <gate list>. The <function> is passed the + gates' original arguments and should return a function, plus possibly + a state and also a variable; then the function will be called repeatedly + with the state and variable and the gates will be called on what the + function returns until it returns "nil" as the first argument. What + the gates return is ignored until the last iteration, in which case + it is passed to the next gate (unless |autoreturn| is set). To delete + a gate's iterator, use the same action with "nil" as <function> (but + that's generally not a good idea, since gates with iterators are + tailored to the arguments the iterator returns, not to the arguments + they are called with). + > list (<table>) Declares an l-gate whose name is the entry at index 1 in <table>. Additional entries can be specified as with |def|. Tables at indices diff --git a/Master/texmf-dist/doc/generic/gates/gates.tex b/Master/texmf-dist/doc/generic/gates/gates.tex index 705a7243f54..c535686cddb 100755 --- a/Master/texmf-dist/doc/generic/gates/gates.tex +++ b/Master/texmf-dist/doc/generic/gates/gates.tex @@ -5,7 +5,7 @@ % Author: Paul Isambert. % E-mail: zappathustra AT free DOT fr % Comments and suggestions are welcome. -% Date: December 2011. +% Date: May 2012. \csname Come on, Gates is already loaded\endcsname \expandafter\let\csname Come on, Gates is already loaded\endcsname\endinput diff --git a/Master/texmf-dist/tex/generic/gates/gates.lua b/Master/texmf-dist/tex/generic/gates/gates.lua index d5ea2c4ae88..826e1a13f3c 100644 --- a/Master/texmf-dist/tex/generic/gates/gates.lua +++ b/Master/texmf-dist/tex/generic/gates/gates.lua @@ -5,7 +5,7 @@ -- Author: Paul Isambert. -- E-mail: zappathustra AT free DOT fr -- Comments and suggestions are welcome. --- Date: December 2011. +-- Date: May 2012. if gates then return @@ -22,16 +22,32 @@ else end print_error = error end -local unpack = table.unpack or unpack +local unpack, maxn = table.unpack or unpack +if lua.version == "Lua 5.1" then + maxn = table.maxn +else + local pairs, type = pairs, type + function maxn (t) + local max = 0 + for i in pairs(t) do + if type(i) == "number" then + max = i > max and i or max + end + end + return max + end +end +local function unpackargs (t) + return unpack(t, 1, maxn(t)) +end local function _copy (tb) local t = {} for a, b in pairs (tb) do if type(b) == "table" then - t[a] = _copy(b) - else - t[a] = b + b = _copy(b) end + t[a] = b end return t end @@ -97,13 +113,22 @@ local function _getinfo (g) local c = tostring(g.conditional) local l = g.loop and tostring(g.loop) local u = g.loopuntil and tostring(g.loopuntil) - local a = g.autoreturn and " (autoreturn)" or "" - return s, c, l, u, a + local i = g.iterator and tostring(g.iterator) + local a + if type(g.autoreturn) == "function" then + a = " (autoreturn: " .. tostring (g.autoreturn) .. ")" + elseif g.autoreturn then + a = " (autoreturn)" + else + a = "" + end + return s, c, l, u, i, a end +local shown local function _show (gate, tab) local g = _rawgate(gate.name) - local s, c, l, u, a = _getinfo(gate) + local s, c, l, u, i, a = _getinfo(gate) local info = _format(tab .. "%s (%s-gate) (status: %s) (conditional: %s)%s", gate.name, g.list and "l" or "m", s, c, a) if l then @@ -112,15 +137,24 @@ local function _show (gate, tab) if u then info = _format(info .. " (loopuntil: %s)", u) end + if i then + info = _format(info .. " (iterator: %s)", i) + end _write(info) if g.list then - for _, G in ipairs(g.list) do - _show(G, tab .. ". ") + if shown[g.name] then + texio.write(" [subgates already shown]") + else + shown[g.name] = true + for _, G in ipairs(g.list) do + _show(G, tab .. ". ") + end end end end local function show (fam, name) + shown = {} name = _check_family(fam, name) local g = _rawgate(name) if g then @@ -163,7 +197,8 @@ local function _getmax (tb) return max end local function _restoreargs (arg, orarg, autoreturn) - if autoreturn then + -- autoreturn may be a function + if autoreturn == true then local argmax, orargmax = _getmax(arg), _getmax(orarg) for i = argmax + 1, orargmax do arg[i] = orarg[i] @@ -171,65 +206,93 @@ local function _restoreargs (arg, orarg, autoreturn) end return arg end +local function _processfunction (fct, autoreturn, ...) + if type(autoreturn) == "function" then + return {autoreturn(...)} + elseif autoreturn then + local newarg = {fct(...)} + local argmax, newargmax = _getmax(arg), _getmax(newarg) + for i = newargmax + 1, argmax do + newarg[i] = arg[i] + end + return newarg + else + return {fct(...)} + end +end -execute.__index = function (_, name) - local f = _rawgate(name) - if f then - return function (...) - local s = "" - if f.status == _open or f.status == _ajar then - if f.status == _ajar then - s = " (ajar -> close)" - f.status = _close +local function _execute (f, sub, ...) + local name, list, fct, status, conditional, loop, loopuntil, iterator, autoreturn = + f.name, f.list, sub and execute[f.name] or f.fct, f.status, f.conditional, f.loop, f.loopuntil, f.iterator, f.autoreturn + local action, report, t + if sub then + action, report, t = "Calling", _reporttab, "subgate " .. name + else + action, report, t = "Executing", _report, (list and "l" or "m") .. "-gate " .. name + end + local s = "" + local orarg, arg = {...}, {...} + if status == _open or status == _ajar then + if status == _ajar then + s = " (ajar -> close)" + f.status = _close + end + if conditional(...) then + report(name, "%s %s%s", action, t, s) + if not sub and not list and _trace_value(name) > 1 then + for i=1, maxn(arg) do + _write_nl(_white .. "#" .. i .. "<-" .. tostring(arg[i])) end - if f.conditional(...) then - _report(name, "Executing %s-gate %s", f.list and "l" or "m", name, s) - if not f.list and _trace_value(name) > 1 then - local arg = _copy({...}) - for a, b in pairs(arg) do - arg[a] = b - for i = 1, a-1 do - arg[i] = arg[i] or "nil" - end - end - for a, b in ipairs(arg) do - b = tostring(b) - -- Can't use _write(), because it calls string.format(), - -- and b might contain literal %'s. - _write_nl(_white .. "#" .. a .. "<-" .. b) - end - end - local orarg, arg = {...}, {...} - if f.loop then - while f.loop(unpack(arg)) do - arg = {f.fct(unpack(arg))} - orarg = _restoreargs(arg, orarg, f.autoreturn) - end - return unpack(arg) - elseif f.loopuntil then - repeat - arg = {f.fct(unpack(arg))} - orarg = _restoreargs(arg, orarg, f.autoreturn) - until f.loopuntil(unpack(arg)) - return unpack(arg) + end + if loop then + while loop(unpackargs(arg)) do + arg = {fct(unpackargs(arg))} + orarg = _restoreargs(arg, orarg, autoreturn) + end + elseif loopuntil then + repeat + arg = {fct(unpackargs(arg))} + orarg = _restoreargs(arg, orarg, autoreturn) + until loopuntil(unpackargs(arg)) + elseif iterator then + local iter, state, var = iterator(unpackargs(arg)) + while iter do + local args = {iter(state, var)} + if args[1] ~= nil then + arg = {fct(unpackargs(args))} + orarg = _restoreargs(arg, orarg, autoreturn) + var = args[1] else - arg = {f.fct(unpack(arg))} - orarg = _restoreargs(arg, orarg, f.autoreturn) - return unpack(arg) + break end - else - _report(name, "Ignoring %s-gate %s%s (False conditional)", f.list and "l" or "m", name, s) end else - if f.status == _skip then - s = " (skip -> open)" - f.status = _open - else - s = " (close)" - end - _report(name, "Ignoring %s-gate %s%s%s", f.list and "l" or "m", name, s, f.conditional() and "" or " (False conditional)") - return ... + arg = {fct(unpackargs(arg))} + orarg = _restoreargs(arg, orarg, autoreturn) end + if type(autoreturn) == "function" then + arg = {autoreturn(...)} + end + else + report(name, "Ignoring %s%s (False conditional)", t, s) + end + else + if status == _skip then + s = " (skip -> open)" + f.status = _open + else + s = " (close)" + end + report(name, "Ignoring %s%s%s", t, s, conditional() and "" or " (False conditional)") + end + return unpackargs(arg) +end + +execute.__index = function (_, name) + local f = _rawgate(name) + if f then + return function (...) + return _execute(f, nil, ...) end else _error_nogate(name) @@ -454,6 +517,14 @@ local function loopuntil (fam, fct, list, cond) end end +local function iterator (fam, fct, list, cond) + if type(list) == "string" then + _status(fam, fct, list, "iterator", cond) + else + _status(fam, fct, nil, "iterator", list) + end +end + local function autoreturn (fam, fct, list, cond) if type(list) == "string" then _status(fam, fct, list, "autoreturn", cond) @@ -472,50 +543,18 @@ local function _store (tab) conditional = tab.conditional or _evertrue, loop = tab.loop, loopuntil = tab.loopuntil, + iterator = tab.iterator, autoreturn = tab.autoreturn} if not fct then tb.list = {} fct = function (...) _addtab() - local orarg, arg = {...}, {...} + local arg = {...} for n, func in ipairs(tb.list) do - local s = "" - if func.status == _open or func.status == _ajar then - if func.status == _ajar then - s = " (ajar -> close)" - func.status = _close - end - if func.conditional(unpack(arg)) then - _reporttab(func.name, "Calling subgate %s%s", func.name, s) - if func.loop then - while func.loop(unpack(arg)) do - arg = {execute[func.name](unpack(arg))} - orarg = _restoreargs(arg, orarg, func.autoreturn) - end - elseif func.loopuntil then - repeat - arg = {execute[func.name](unpack(arg))} - orarg = _restoreargs(arg, orarg, func.autoreturn) - until func.loopuntil(unpack(arg)) - else - arg = {execute[func.name](unpack(arg))} - orarg = _restoreargs(arg, orarg, func.autoreturn) - end - else - _reporttab(func.name, "Ignoring subgate %s%s (False conditional)", func.name, s) - end - else - if func.status == _skip then - s = " (skip -> open)" - tb.list[n].status = _open - else - s = " (close)" - end - _reporttab(func.name, "Ignoring subgate %s%s%s", func.name, s, func.conditional() and "" or " (False conditional)") - end + arg = {_execute(func, true, unpackargs(arg))} end _untab() - return unpack(arg) + return unpackargs(arg) end end tb.fct = fct @@ -540,7 +579,7 @@ local function list (fam, tab) g[1] = _check_family(fam, g[1]) if type(g[2]) == "function" then _store({g[1], g[2]}) - elseif type(g[2]) == "table" then + elseif type(g[2]) == "table" or type(g[2]) == "string" then local a = {} for i = 2, #g do _insert(a, g[i]) @@ -562,6 +601,8 @@ local function list (fam, tab) _status(fam, g[1], tab[1], "loop", l) local L = type(g.loopuntil) == "function" and g.loopuntil _status(fam, g[1], tab[1], "loopuntil", L) + local I = type(g.iterator) == "function" and g.iterator + _status(fam, g[1], tab[1], "iterator", I) _status(fam, g[1], tab[1], "autoreturn", g.autoreturn) end end @@ -580,6 +621,7 @@ local function copy (fam, name1, name2) _status(fam, name1, nil, "conditional", base.conditional) _status(fam, name1, nil, "loop", base.loop) _status(fam, name1, nil, "loopuntil", base.loopuntil) + _status(fam, name1, nil, "iterator", base.iterator) else _error_nogate(name2) end @@ -632,6 +674,7 @@ local _mt = { conditional = conditional, loop = loop, loopuntil = loopuntil, + iterator = iterator, autoreturn = autoreturn, subgates = subgates, } diff --git a/Master/texmf-dist/tex/generic/gates/gates.sty b/Master/texmf-dist/tex/generic/gates/gates.sty index 28152eacb60..61d725b0a6a 100644 --- a/Master/texmf-dist/tex/generic/gates/gates.sty +++ b/Master/texmf-dist/tex/generic/gates/gates.sty @@ -5,10 +5,10 @@ % Author: Paul Isambert. % E-mail: zappathustra AT free DOT fr % Comments and suggestions are welcome. -% Date: December 2011. +% Date: May 2012. \NeedsTeXFormat{LaTeX2e} -\ProvidesPackage{gates}[2011/12/03 v.0.1 Modular architecture.] +\ProvidesPackage{gates}[2012/05/26 v.0.2 Modular architecture.] \chardef\formatnumber=4 \input gates.tex \endinput diff --git a/Master/texmf-dist/tex/generic/gates/t-gates.tex b/Master/texmf-dist/tex/generic/gates/t-gates.tex index 0fb80e8d01b..ff2426cd83d 100644 --- a/Master/texmf-dist/tex/generic/gates/t-gates.tex +++ b/Master/texmf-dist/tex/generic/gates/t-gates.tex @@ -5,11 +5,11 @@ % Author: Paul Isambert. % E-mail: zappathustra AT free DOT fr % Comments and suggestions are welcome. -% Date: December 2011. +% Date: May 2012. % %D \module %D [ file=t-gates, -%D version=2011.12.03, +%D version=2012.05.26, %D title=\CONTEXT\ User Module, %D subtitle=Gates, %D author=Paul Isambert, |