Короткая ссылка-перенаправление

Это руководство о том, как написать или преобразовать шаблон с использованием расширения MediaWiki Scribunto. Scribunto — расширение, разработанное Тимом Старлингом и Виктором Васильевым и позволяющее встраивать скриптовые языки в MediaWiki. В настоящее время поддерживается только скриптовый язык Lua. Здесь даётся общий обзор использования Lua, а также ссылки на дополнительную информацию в различных местах.

Шаблоны, использующие Lua, состоят из двух частей: самого шаблона и одного или нескольких модулей в пространстве имён Модуль:, которые содержат программы, запускающиеся на серверах Викимедиа при генерации вики-текста, в который шаблон раскрывается. Шаблон вызывает функцию в модуле, используя функцию парсера по имени {{#invoke:}}.

Идея использования Lua в том, чтобы повысить производительность обработки шаблонов. При этом исчезает надобность в программировании шаблонов функциями парсера, такими как {{#if:}}, {{#ifeq:}}, {{#switch:}} и {{#expr:}}. Вместо этого всё делается в модуле, на языке, который был действительно разработан как язык программирования, а не система шаблонов, к которой были со временем прикручены различные расширения, чтобы попытаться получить из неё язык программирования. Использование Lua также делает ненужным раскрытие шаблонов в последующие шаблоны, что может привести к упиранию в предел глубины раскрытия. Использующий Lua в полной мере шаблон никогда не нуждается во включении других шаблонов[a].

Lua — это язык, на котором написаны модули. В отличие от системы функций парсера и шаблонов, Lua был действительно предназначен не только быть полноценным языком программирования, но и быть языком программирования, подходящим для так называемых встроенных скриптов. Модули в MediaWiki — пример встроенных скриптов. Есть несколько встроенных скриптовых языков, которые могли бы быть использованы, в том числе REXX и tcl; и, на самом деле, первоначальная цель Scribunto заключалась в возможности выбора таких языков. На данный момент, однако, доступен только Lua.

Исходная спецификация API — стандартной библиотеки функций Lua и переменных, которые должны быть доступны в модулях — дана в MW:Extension:Scribunto/API specification. Однако, даже это не соответствует действительности. То, чем вы на самом деле располагаете, документировано в MW:Extension:Scribunto/Lua reference manual/ru, которая является версией 1-го издания руководства по Lua, урезанной и изменённой Тимом Старлингом, для соответствия реальности вики-проектов. И это также справочник, а не учебник.

Вещи, с которыми вы чаще всего будете иметь дело, разрабатывая шаблоны с использованием Lua — это таблицы, строки, числа, логические значения, nil, if then else end, while do end, for in do end (порождённый for), for do end (числовой for), repeat until, function end, local, return, break, выражения и различные операторы (включая #, .., арифметические операторы +, -, *, /, ^ и %), и глобальные таблицы (библиотеки) string, math и mw. Обратите внимание, оператор сравнения «не равно» в lua выглядит как ~=.

Вызов модуля править

Вызова модуля производится функцией парсера {{#invoke: }}. Вот, например, типичный код вызывающего модуль шаблона {{ref label}}:

<includeonly>{{#invoke:citation|reflabel}}</includeonly><noinclude>{{documentation}}</noinclude>

В нём мы видим вызов функции reflabel из модуля citation без непосредственно передаваемых в коде шаблона в модуль параметров, которые бы шли после. Не следует помещать в вызов модуля другие шаблоны, функции парсера или переменные в качестве его аргументов, это лишь замедлит вызов модуля.

Вызванный из шаблона модуль будет иметь доступ к объекту frame на один уровень вверх (родительскому), в частности — будет иметь данные о имени страницы, где был вызван данный шаблон, о том, с какими параметрами шаблон был вызван, то есть в такой ситуации не нужен проброс переменных (указание в шаблоне при вызове модуля {{{1|}}} в качестве аргументов). В случае, если страница, на которой производится представление результата работы шаблонов и модулей пользователю, разворачивает первый шаблон, а он в свою очередь разворачивает второй шаблон, который вызывает модуль — в такой ситуации родительским объектом frame для модуля будет объект, связанный с первым шаблоном, и без дополнительных действий получить параметры вызова первого шаблона на странице из модуля не получится, см. #Получение аргументов шаблона.

Основы построения модуля править

Общая структура править

Рассмотрим гипотетический модуль, модуль:Population. (См. en: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:}}, выбирается из таблицы, построенной на первом этапе, и вызывается с аргументами шаблона и аргументами {{#invoke:}} (подробнее об этом ниже).

Первый скрипт на Lua выполняет вполне ясным образом первый этап. Он создаёт локальную переменную с именем p в строке 1, при инициализации таблицы; создаёт и добавляет функцию к ней (строки 3—5), сообщая функции имя India в таблице, названной p (function p.India — то же самое, что и p["India"] = function[b]); а потом, в последней строке 7 скрипта, таблица возвращается. Функции, которые не будут привязаны к возвращаемой модулем таблице при вызове модуля из шаблона, называются локальными функциями вашего модуля — другие модули не могут вызывать их, также они недоступны для вызова со страниц Википедии, однако данные подпрограммы можно использовать в других функциях вашего модуля, которые определены ниже (в области видимости локальных функций). Возвращаемую локальную переменную необязательно называть p (её необязательно вообще как-то называть, в некоторых случаях удобно начинать свой модуль сразу с return, как можно увидеть на примере второго модуля). Ей можно дать любое допустимое в Lua имя переменной[c], это имя, которое можно будет использовать для тестирования скрипта в отладочной консоли редактора модулей.

Второй Lua-скрипт делает то же самое, но более «идиоматически». Вместо создания имени переменной в таблице он создает анонимную таблицу на лету, внутри выражения return, которое является единственным (из выполняемых на первом этапе) оператором в сценарии. India = function(frame) end в строках 2—4 создаёт (также анонимную) функцию и вставляет её в таблицу под названием India. Чтобы дополнить такой скрипт бо́льшим числом (вызываемых) функций, их добавляют в качестве дополнительных полей в таблице. (Невызываемые локальные функции могут, опять же, добавляться перед самим оператором return.)

В обоих случаях код шаблона будет {{#invoke:Population|India}}, чтобы вызвать функцию под названием India из модуля Модуль:Population. Также обратите внимание, что выражение function определяет функцию как объект, который будет вызываться. Оно не объявляет её, к чему вы, вероятно, привыкли в других языках программирования; функция не выполняется, пока её не вызвали.

Можно, конечно, делать и более сложные вещи. Например, можно объявить другие локальные переменные в дополнение к p, чтобы хранить таблицы данных (такие как списки языков или названия стран), используемые модулем. Но это базовая структура модуля. Вы делаете таблицу, наполненную чем-то, и возвращаете её.

Получение аргументов шаблона править

Обычная функция Lua может принимать (практически) произвольное число аргументов. Посмотрите на эту функцию из en:Module:Wikitext, которую можно вызвать с числом аргументов от одного до трёх:

function z.oxfordlist(args,separator,ampersand)

Функции, вызываемые {{#invoke:}} — особый случай. Они рассчитывают получить ровно один аргумент — таблицу, которая называется фрейм (потому её параметр в списке параметров функции обычно называют frame). Она называется фрейм, потому что, увы, разработчики решили назвать её так для своего удобства. Это имя дано в честь внутренней структуры в коде самого MediaWiki, которую он как бы представляет[d].

Этот фрейм содержит (под)таблицу под названием args. Также он поддерживает средства для доступа к своему родительскому фрейму (опять же названному в честь кое-чего из MediaWiki). В родительском фрейме также есть (под)таблица, также названная args.

  • Параметры в дочернем фрейме — то есть значении параметра frame функции — это параметры, переданные {{#invoke:}} в тексте шаблона. Так, например, если Вы написали {{#invoke:Population|India|a|b|class="popdata"}} в коде шаблона, то подтаблица параметров дочернего фрейма будет (в форме Lua) { "a", "b", class="popdata" }.
  • Параметры в родительском фрейме — это параметры, переданные в шаблон, когда он был включён. Так, например, если пользователь шаблона пишет {{население Индии|с|d|язык=хинди}}, то подтаблица параметров из родительского фрейма будет (в форме Lua) {"c", "d", ["язык"] = "хинди"}.

Можно использовать удобную идиому программирования, чтобы сделать это немного легче — иметь локальные переменные с именами, допустим, config и args в функции для ссылки на эти две таблицы параметров. Пример из Module:Citation:

-- This is used by template {{efn}}.
function z.efn(frame)
    local pframe = frame:getParent()
    local config = frame.args -- параметры, переданные САМИМ ШАБЛОНОМ в тексте САМОГО ШАБЛОНА
    local args = pframe.args -- параметры, переданные ШАБЛОНУ в тексте ВЫЗВАВШЕЙ СТРАНИЦЫ

Всё в config, таким образом, — это параметры, которые Вы указали в вашем шаблоне; они доступны с помощью кода типа config[1] и config.class. Они сообщат функции из модуля её «настройки» (например, имя класса CSS, которое может варьироваться в зависимости от того, в каком шаблоне она используется).

А всё в args — это параметры, которые укажет пользователь шаблона там, куда его включит, которые доступны через код типа args[1] и args["язык"]. Это будут обычные аргументы шаблона, описанные на его /doc-подстранице.

Для обоих наборов параметров имя и значение параметра будет точно таким же, как в тексте, за исключением начальных и конечных пробельных символов. На Ваш код повлияет применение параметров включения из вызова, не являющихся корректными именами переменных в Lua. Тогда вместо формы с точкой (args.language) понадобится обращение в форме с квадратными скобками (args["язык"]).

Именованные параметры перечислены в таблицах args, разумеется, по названию. Нумерованные параметры (как в результате явного 1=, так и позиционные) индексируются в них числом, а не строкой. args[1] не тождественно args["1"], и последнее никак не установить из викитекста.

Наконец, отметим, что модули Lua могут различать параметры, которые были использованы в викитексте и просто установлены в пустую строку, и параметры, которых нет вообще. Последние не существуют в таблице args, и обращение к ним даст nil — а первые существуют и равны пустой строке "" (логически положительному значению).

Ошибки править

Давайте скажем одну вещь с самого начала: Ошибка выполнения — это гиперссылка. Вы можете поместить на неё указатель мыши и нажать.

Если у вас в браузере включён JavaScript, при этом появится всплывающее окно с подробностями ошибки, стеком вызовов и даже гиперссылками на место в коде, где произошла ошибка в соответствующем модуле.

Вы можете сгенерировать ошибку функцией error().

Советы и хитрости править

Таблицы аргументов — «особенные» править

По причинам, лежащими за рамками настоящего руководства[e], подтаблица args фрейма не совсем похожа на обычную таблицу. Она поначалу пуста, и заполняется параметрами тогда и в той мере, в какой код к ним обращается[f]. (Собственные таблицы тоже можно сделать особенными, присоединив к ним метатаблицу; это также за рамками данного руководства).

Неприятный побочный эффект этого заключается в том, что некоторые нормальные операторы для таблиц в Lua не работают с args. Оператор длины # и функции из библиотеки table работают только в стандартных таблицах, и на особенных таблицах args не получаются. Тем не менее, pairs() и ipairs() будут работать, так как код для того, чтобы сделать их использование возможным, был добавлен разработчиками.

Копируйте содержимое таблиц в локальные переменные править

Имя в Lua — это либо доступ к локальной переменной, либо поиск по таблице[2]. math.floor, например, — это поиск ключа "floor" в глобальной таблице math. Поиск по таблице выполняется медленнее, чем поиск локальной переменной. Поиск в таких таблицах, как frame.args, при их «особенности» — намного медленнее.

Функция в Lua может содержать до 250 локальных переменных[3]. Так что пользуйтесь ими свободно:

  • Если вызываете math.floor много раз, скопируйте её в локальную переменную и используйте через неё:[3]
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.xxx снова и снова. Скопируйте его значение в локальную переменную и используйте через неё:
local Tab = args.tab

(Даже сама переменная args является способом избежать повторные поиски args в таблице frame.)

  • Приём для альтернативных названий одного и того же параметра. Если параметр шаблона может быть передан под разными названиями — например, в прописной и строчной форме или на русском и на английском — можно использовать оператор Lua or, чтобы выбрать наиболее приоритетное имя, которое фактически поставлено:
local Title = args["энциклопедия"] or args["Энциклопедия"] or args.encyclopaedia or args.encyclopedia or args.dictionary
local ISBN = args.isbn13 or args.isbn or args.ISBN

Это работает по двум причинам:

  • nil и false эквивалентны в логических операциях.
  • В Lua оператор or имеет, что называется, семантику «сокращения»: если левый операнд имеет значение, отличное от nil или false, он даже не вычисляет значение правого операнда. (Таким образом, хотя пример может на первый взгляд выглядеть как четыре поиска, в самых частых случаях, когда в шаблоне используется |энциклопедия=, поиск на самом деле производится один).
  • Приём по умолчанию — пустая строка. Иногда полезно, чтобы незаданный параметр шаблона давал nil. В других случаях, однако, Вы можете хотеть, чтобы недостающие параметры были эквивалентны пустым строкам. Для этого достаточно просто поставить в конце выражения or "":
local ID = args.id or args.ID or args[1] or ""

Не раскрывайте шаблоны, даже несмотря на то, что можете править

Если локальные переменные дёшевы, а поиск по таблице — дорог, то раскрытие шаблона намного выше Вашего потолка по цене.

Бегите от frame:preprocess(), как от чумы. Раскрытие вложенных шаблонов через препроцессор MediaWiki — это то, от чего мы, в конце-то концов, и пытаемся уйти. Большинство вещей, которые Вы бы могли сделать так, делаются проще, быстрее и удобнее простыми функциями Lua.

Аналогичным образом, не храните в шаблоне то, что фактически является записью в базе данных. Чтение это будет вложенным вызовом парсера с сопутствующими запросами к базе данных, и всё это затем, чтобы сопоставить одной строке другую. Просто сделайте модуль с табличными данными, как Модуль:Languages/data.

Отладка править

В Scribunto доступна отладка — вы можете ввести имя целевой страницы, на которой разворачивается использующий данный модуль шаблон, нажать кнопку «Предварительный просмотр страницы с использованием этого шаблона или модуля», и увидеть результат работы того кода, который вы только что правили и который пока не сохранён.

Также вы можете работать с функциями lua и возвращаемой вашим модулем таблицей из консоли. Так как в качестве аргумента функции, которые вызываются со страниц Википедии, должны получать объекты типа frame, то и из консоли их следует вызывать таким же образом. Это можно сделать, например, строкой =p.main(mw.getCurrentFrame():newChild{title="smth",args={"arg1","arg2",["namedArg1"]="1",["namedArg2"]="2"}}), где main — это название функции, которая прикреплена к возвращаемой модулем таблице, mw.getCurrentFrame():newChild{ } — встроенные функции, которые создают объект frame с заданными параметрами, который будет передан функции для выполнения, smth — обязательная заглушка, в некоторых случаях вам необходимо будет получать доступ к названию страницы, откуда был вызван модуль и вам потребуется осмысленное значение этого параметра при отладке, далее идут неименованные аргументы, аналогичные записи {{#invoke:MyModule|main|arg1|arg2|namedArg1=1|namedArg2=2}} — обратите внимание что цифры передаются как текст. Подробнее см. Проект:Технические работы/Модули#ЧаВо.

Примечания править

  1. Они могут быть нужны до той поры, когда весь предоставляемый функционал станет доступен модулям Scribunto, чтобы включать волшебные слова (см. #Советы и хитрости). Волшебные слова, тем не менее, — не шаблоны.
  2. Изобретатели языка называют это «синтаксическим сахаром»[1].
  3. en:Module:Citation, например, использует z.
  4. В MediaWiki как таковом фреймов больше двух.
  5. Если хотите знать, прочтите о том, как MediaWiki — отчасти из-за бремени, наложенного на него старой системой шаблонов-условно-включающих-шаблоны — выполняет ленивое вычисление параметров.
  6. Поэтому не удивляйтесь, увидев в стеке вызовов обращение к какому-то другому модулю там, где, как вы думали, вы всего лишь ссылаетесь на параметр шаблона. Это произошло потому, что тот параметр включал другой шаблон, использующий Scribunto.

Ссылки править

Перекрёстные ссылки править

Источники править

  • Ierusalimschy, Roberto; de Figueiredo, Luiz Henrique; Celes, Waldemar (12 May 2011). "Passing a Language through the Eye of a Needle". Queue. Association for Computing Machinery. 9 (5). ACM 1542-7730/11/0500. {{cite journal}}: Недопустимый |ref=harv (справка)
  • Ierusalimschy, Roberto. Lua Performance Tips // Lua Programming Gems. — Lua.org, December 2008. — ISBN 978-85-903798-4-3.

Дальнейшее чтение править