跳至內容

用戶: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