跳转到内容

User:Te0sla/Wikipedia:Guide to Scribbling

维基百科,自由的百科全书
"Shh! I'm reading about how to Scribble templates."

这是 涂涂画画(Scribbling)的指南. 涂涂画画, 是编写模板或转换模板的行为, 通过它实现the Scribunto extensionMediaWiki.Scribunto 扩展[a]Tim StarlingVictor Vasiliev开发, 允许在MediaWiki里嵌入语言。目前唯一支持的语言是Lua。 本指南意在给您提供一个关于Scribbling的广泛概述, 以及各个方向进一步资料的提示。

潦草的模板分为两部分: 模板本身和一个或多个后端模块(位于 Module: namespace) ,这些模块包含在WIKI服务器上运行的程序,用于生成模板扩展到的 wikitext。模板使用名为 {{#invoke:}}的新解析器函数调用模块中的函数。

涂鸦的思想是为了提高模板处理的性能。通过使用分析器函数,例如{{#if}}, {{#ifeq}}, {{#switch}} and {{#expr}} ,Scribbling 消除了模板分析器函数编程的任何需要。所有这些都是在模块中完成的,用的是一种实际上被设计成编程语言的语言,而不是一个模板系统,随着时间的推移,这个模板系统被固定在各种各样的扩展上,试图把它变成一种编程语言。[b] 涂鸦也消除了模板扩展到进一步模板的需要,并有可能达到扩展深度的限制。一个完整的涂涂画画模板应该永远不需要交换其他模板。[c]

Lua[编辑]

用来编写模块的语言是 Lua。与模板解析器函数系统不同,Lua 实际上不仅被设计成一种合适的编程语言,而且被设计成一种适用于所谓嵌入式脚本的编程语言。MediaWiki 中的模块是嵌入式脚本的一个示例。有几种嵌入式脚本语言可以使用,包括 REXXtcl,事实上,涂涂画画最初的目标是提供这些语言的选择。然而,目前只有 Lua 可用。

Lua 的官方参考手册是Ierusalimschy, de Figueiredo & Celes 2006。这是一个参考,不是一个教程。如果您想了解某些内容的语法或语义,请参考它。有关教程,请参见Ierusalimschy 2006(也可以使用Ierusalimschy 2003,尽管它当然是过时的。)或Jung & Brown 2007。这些书的缺点是,它们告诉你的很多事情与在 MediaWiki 模块中使用 Lua 没有任何关系。您不需要知道如何安装 Lua,以及如何将它的解释器集成到程序中或独立运行它。MediaWiki 开发人员已经完成了所有的工作。同样,出于安全考虑,许多 Lua 库函数在模块中是不可用的。(例如,不可能在 MediaWiki 模块中执行文件 I/O或进行操作系统调用。)因此,这些书对 Lua 标准库函数和随语言而来的变量的解释,在这里要么是不相关的,要么是不真实的。

最初的 API 规范— Lua 标准库函数和变量,应该可以在模块中使用 —在MW:Extension:Scribunto/API specification规范中给出。然而,即便如此,这也是不真实的。你“实际上”可以使用的是MW:Extension:Scribunto/Lua reference manual 参考手册,它是第一版 Lua 手册的精简版,由 Tim Starling 编辑和修改,使之更符合 Scribbling 的实际情况。再次强调,这是一本参考手册,而不是教程。

在Lua中,在编写潦草模板中,你最关心的事情是表格字符串数字布尔值nil, if then else end, while do end, for in do end(generated for),for do end (numerical for), repeat until, function end, local, return, break,和各种操作符(包括 #, ..,算术运算符+, -, *, /, ^,和%),以及 string, math, 和 mw 全局表(即库)。

模板结构[编辑]

这很简单。通常情况下,您的模板包含{{#invoke:}}的一个扩展。下面举一个{{Harvard citation}}的例子:

<includeonly>{{Harvard citation/core
|BracketLeft=(
|BracketRight=)
|P1={{{1|}}}
|P2={{{2|}}}
|P3={{{3|}}}
|P4={{{4|}}}
|P5={{{5|}}}
|REF={{{ref|{{{Ref|}}}}}}
|Location={{{loc|}}}
|Page={{{p|}}}
|Pages={{{pp|}}}
|PageSep=,p. 
|PagesSep=,pp. 
}}</includeonly><noinclude>
{{documentation}}
</noinclude>

如果您发现自己想在模板中使用其他模板,或者使用模板解析器函数,或者除了{{#invoke:}}和一些可能的变量作为参数之外的其他任何东西,那么您使用的方法是错误的

模块基础[编辑]

整体结构[编辑]

让我们思考一个假设的模块,Module:Population。(参见Module:Population clocks,用于类似但更复杂的模块。)它可以有两种结构:

一个命名的本地表[编辑]

local p = {}

function p.India(frame)
    return "1,21,01,93,422 people at (nominally) 2011-03-01 00:00:00 +0530"
end

return p

动态生成的未命名表[编辑]

return {
    India = function(frame)
        return "1,21,01,93,422 people at (nominally) 2011-03-01 00:00:00 +0530"
    end
}

执行[编辑]

{{#invoke:}}对模块的执行实际上是双重的:

  1. 加载模块并运行整个脚本。这将加载模块需要的任何其他模块(使用require()函数) ,构建模块将提供给模板的(可调用的)函数,并返回它们的表。
  2. {{#invoke:}} 中命名的函数是从阶段1中构建的表中挑选出来并调用的,使用提供给模板的参数提供给{{#invoke:}}(稍后将详细介绍)的参数。

第一个 Lua 脚本相当明确地执行阶段1。它在第1行创建一个名为p 的局部变量(local variable),初始化为一个表; 构建并向其添加一个函数(第3–5行) ,方法是在以p命名的表中给函数取名为India(函数function p.Indiap["India"] = function相同[d]) ; 然后返回(第7行)该表作为脚本的最后一行。要使用更多(可调用)函数展开这样的脚本,需要在顶部的本地语句和底部的return语句之间添加这些函数。(可以在local语句之前添加不可调用的 local 函数。)局部变量不一定要命名为p。它可以命名为您喜欢的任何有效的 Lua 变量名。p只是用于此目的的常规名称,也是您可以在 Module 编辑器的调试控制台中用来测试脚本的名称。

第二个 Lua 脚本执行相同的操作,但是更“习惯性”。它不是将命名变量创建为表,而是动态地在return语句中间创建一个匿名表,而 return 语句是脚本中唯一的(在第一阶段执行)语句。India = function(frame) end在第2–4行结束时创建一个(也是匿名的)函数,并将其插入到表中,名为India。要使用更多(可调用)函数展开这样的脚本,可以将它们作为表中的进一步字段添加。(同样,可以在return 语句之前添加不可调用的局部函数。)

在这两种情况下,人们编写的模板代码都是{{#invoke:Population|India}},以便从Module:Population调用名为India的函数。还要注意,function构建了一个函数,作为一个对象,被调用。它不 声明 它,因为你可能已经习惯了从其他编程语言,并且函数直到调用才执行。

One can do more complex things than this, of course. For example: One can declare other local variables in addition to p, to hold tables of data (such as lists of Language or country names), that the module uses. But this is the basic structure of a module. You make a table full of stuff, and return it.

当然,一个人可以做比这更复杂的事情。例如: 除了p之外,还可以声明其他局部变量,以保存模块使用的数据表(如语言或国家名称列表)。但这是模块的 基本结构 。You make a table full of stuff, and return it。

接受模板参数[编辑]

Lua 中的普通函数可以接受(有效地)任意数量的参数。这个函数来自Module:Wikitext ,可以在0到3个参数之间调用:

function z.oxfordlist(args,separator,ampersand)

{{#invoke:}} 调用的函数是特殊的。它们希望只传递一个参数,即一个称为frame的表(因此通常在函数的参数列表中给出参数名 frame )。它之所以被称为框架,是因为不幸的是,开发人员为了方便起了这个名字。它是以 MediaWiki 本身的代码中的一个内部结构命名的,它在某种程度上代表了这个内部结构。[e]

这个框架中有一个(子)表,名为args。它还有一个访问其父框架的方法(同样,以 MediaWiki 中的一个事物命名)。父框架中有一个(子)表,也称为args

  • 在(child,one supposes) frame 中的参数— 也就是框架参数对函数的值 —是在模板的 wikitext 中传递给{{#invoke:}}的参数。例如,如果在模板中编写{{#invoke:Population|India|a|b|class="popdata"}} ,那么子框架的参数子表将是{ "a", "b", class="popdata" }
  • 父框架中的参数是传递给模板时传递给模板的参数。例如,如果您的模板的用户写 {{Population of India|c|d|language=Hindi}},那么父框架的参数子表将是(以 Lua 形式编写的){ "c", "d", language="Hindi" }

为了让这一切变得更简单,可以使用一个方便的程序员的习惯用法,即在函数中使用名为(比如)configargs的局部变量,它们指向这两个参数表。看看这个,来自 Module:WikidataCheck:

function p.wikidatacheck(frame)
	local pframe = frame:getParent()
	local config = frame.args -- the arguments passed BY the template, in the wikitext of the template itself
	local args = pframe.args -- the arguments passed TO the template, in the wikitext that transcludes the template

因此,config 中的所有内容都是您在模板中指定的一个参数,您可以使用诸如 config[1]config["class"]之类的代码进行引用。这些信息告诉你的模块函数它的"配置"(例如,一个 CSS 类名可以根据使用的模板而变化)。

因此,args中的所有内容都是模板的用户指定的一个参数,在模板被转换的地方,您可以引用诸如args[1]args["language"]之类的代码。这些将是正常的模板参数,正如在模板/doc页面中记录的那样。

参见{{other places}}和{{other ships}},两个模板都执行{{#invoke:Other uses|otherX|x}} ,但使用不同的参数代替x,从而从一个公共 Lua 函数获得不同的结果。

For both sets of arguments, the name and value of the argument are exactly as in the wikitext, except that leading and trailing whitespace in named parameters is discounted. This has an effect on your code if you decide to support or employ transclusion/invocation argument names that aren't valid Lua variable names. You cannot use the "dot" form of table lookup in such cases. For instance: args.author-first is, as you can see from the syntax colourization here, not a reference to an |author-first= argument, but a reference to an |author= argument and a first variable with the subtraction operator in the middle. To access such an argument, use the "square bracket" form of table lookup: args["author-first"].

对于这两组参数,参数的名称和值与 wikitext 中完全一样,只是命名参数中的前导和尾随空格打了折扣。如果您决定支持或使用无效的 Lua 变量名称的 transclusion/invocation 参数名称,则会对代码产生影响。在这种情况下,不能使用表格查找的“点”形式。例如: args.author-first 是一个引用|author-first=的引用,而第一个变量的减法运算符位于中间。要访问这样的参数,请使用表格查找的“方括号”形式: args["author-first"]

当然,命名参数在args表中按照它们的名称字符串进行索引。位置参数(无论是否作为显式1=的结果)在args表中按编号而不是按字符串进行索引。args[1]不同于args["1"],后者实际上是 wikitext 中不可设置的。

最后,注意 Lua 模块可以区分 wikitext 中使用的参数和根本不在 wikitext 中的参数。后者在args表中不存在,任何索引它们的尝试都将计算为nil。而前者确实存在于表中,并且计算为空字符串,""

错误[编辑]

让我们从一开始就搞清楚一件事: 脚本错误 是一个超链接。你可以把鼠标指针放在上面然后点击。

我们已经变得如此局限于我们(non-Scribbled)把错误消息放在红色的模板中,以至于我们认为 Scribunto“ Script error”错误消息只不过是相同的。不是的。如果你在你的 WWW 浏览器中启用了JavaScript,它会弹出一个窗口,提供错误的细节,一个回调跟踪,甚至超链接,它会把你带到错误发生在相关模块的代码位置。

您可以通过调用error()函数来导致错误发生。

技巧和窍门[编辑]

参数表是“特殊的”。[编辑]

由于超出本指南范围的原因,[f]框架的args子表与普通表格不太一样。它从空开始,并且在执行查找它们的代码时用参数填充。[g](在 Lua 程序中,可以使用称为metatables的东西来创建这样的表。这也超出了本指南的范围。)

这样做的一个不幸的副作用是,一些普通的 Lua 表操作符不能在args表上工作。长度运算符#将不起作用,Lua table库中的函数也不起作用。这些方法只适用于标准的表,而当使用特殊的args表时就会失败。但是,pairs()ipairs()函数都可以工作,因为开发人员已经添加了使其可用的代码。

将表格内容复制到局部变量中。[编辑]

A name in Lua is either an access of a local variable or a table lookup.[3] math.floor is a table lookup (of the string "floor") in the (global) math table, for example. Table lookups are slower, at runtime, than local variable lookups. Table lookups in tables such as the args table with its "specialness" are a lot slower.

Lua 中的名称可以是对本地变量的访问,也可以是查找表。[3]例如,math.floor 是(全局)数学表中的表查找(字符串 "floor")。在运行时,表查找比局部变量查找慢。在诸如 args Table 这样具有“特殊性”的表中查找要慢得多。


Lua 中的一个函数最多可以有 250 个局部变量。[4] 所以自由地使用它们:

  • 如果您多次调用math.floor,请将其复制到局部变量中并使用它:[4]
local floor = math.floor
local a = floor((14 - date.mon) / 12)
local y = date.year + 4800 - a
local m = date.mon + 12 * a - 3
return date.day + floor((153 * m + 2) / 5) + 365 * y + floor(y / 4) - floor(y / 100) + floor(y / 400) - 2432046
  • 不要反复使用args.something。将其复制到局部变量中并这样使用它:
local Tab = args.tab

(Even the args variable itself is a way to avoid looking up "args" in the frame table over and over.)

When copying arguments into local variables there are two useful things that you can do along the way:

  • The alternative names for the same argument trick. If a template argument can go by different names — such as uppercase and lowercase forms, or different English spellings — then you can use Lua's or operator to pick the highest priority name that is actually supplied:
    local Title = args.title or args.encyclopaedia or args.encyclopedia or args.dictionary
    local ISBN = args.isbn13 or args.isbn or args.ISBN
    
    This works for two reasons:
    • nil is the same as false as far as or is concerned.
    • Lua's or operator has what are known as "shortcut" semantics. If the left-hand operand evaluates to something that isn't false or nil, it doesn't bother even working out the value of the right-hand operand. (So whilst that first example may at first glance look like it does four lookups, in the commonest case, where |title= is used with the template, it in fact only actually does one.)
  • The default to empty string trick. Sometimes the fact that an omitted template argument is nil is useful. Other times, however, it isn't, and you want the behaviour of missing arguments being empty strings. A simple or "" at the end of an expression suffices:
    local ID = args.id or args.ID or args[1] or ""
    

即使可以,也不要展开模板。[编辑]

If local variables are cheap and table lookups are expensive, then template expansion is way above your price bracket.

Avoid frame:preprocess() like the plague. Nested template expansion using MediaWiki's preprocessor is what we're trying to get away from, after all. Most things that you'd do with that are done more simply, more quickly, and more maintainably, with simple Lua functions.

Similarly, avoid things like using w:Template:ISO 639 name aze (deleted August 2020) to store what is effectively an entry in a database. Reading it would be a nested parser call with concomitant database queries, all to map a string onto another string. Put a simple straightforward data table in your module, like the ones in Module:Language.

脚注[编辑]

  1. ^ The name "Scribunto" is Latin. "scribunto" is third person plural future active imperative of "scribere" and means "they shall write". "scribble" is of course an English word derived from that Latin word, via Mediaeval Latin "scribillare".[1]
  2. ^ For an idea of what "bolted-on" connotes when it comes to software design, see the Flintstones cartoons where the rack of ribs from the Drive-Thru is so heavy that it causes the Flintstones' car to fall on its side.
  3. ^ It may need, until such time as the whole of the specified API for Scribunto is available to modules, to transclude magic words. See the tips and tricks section. Magic words are not templates, however.
  4. ^ The inventors of the language call this syntactic sugar.[2]
  5. ^ In MediaWiki proper, there are more than two frames.
  6. ^ If you want to know, go and read about how MediaWiki, in part due to the burden laid upon it by the old templates-conditionally-transcluding-templates system, does lazy evaluation of template arguments.
  7. ^ Don't be surprised, therefore, if you find a call backtrace showing a call to some other module in what you thought was an ordinary template argument reference. That will be because expansion of that argument involved expanding another Scribbled template.

参考资料[编辑]

交叉引用[编辑]

引文[编辑]

  • Merriam-Webster's Collegiate Dictionary: Eleventh Edition. Merriam-Webster's Collegiate Dictionary 11th. Merriam-Webster: 1116. 2003. ISBN 9780877798095.  |entry=|title=只需其一 (帮助)
  • Ierusalimschy, Roberto; de Figueiredo, Luiz Henrique; Celes, Waldemar. Passing a Language through the Eye of a Needle. Queue (Association for Computing Machinery). 12 May 2011, 9 (5). ACM 1542-7730/11/0500. 
  • Ierusalimschy, Roberto. Lua Performance Tips (PDF). de Figueiredo, Luiz Henrique; Celes, Waldemar; Ierusalimschy, Roberto (编). Lua Programming Gems. Lua.org. December 2008. ISBN 978-85-903798-4-3. 

进一步阅读[编辑]

Lua[编辑]

Template:Wikipedia technical help