Модуль:Коллективы
Для документации этого модуля может быть создана страница Модуль:Коллективы/doc
-- Bard-Wiki collective pages. Lua is presentation/read-only; storage is in templates.
local p = {}
local groupCache
local function text(v) return mw.text.nowiki(tostring(v or '')) end
local function clean(v) return mw.text.trim(tostring(v or '')) end
local function quote(v) return "'" .. tostring(v):gsub('\\','\\\\'):gsub("'","\\'") .. "'" end
local function split(v, sep)
local out = {}
for _, part in ipairs(mw.text.split(v or '', sep or ';', true)) do
part = clean(part); if part ~= '' then out[#out+1] = part end
end
return out
end
local function link(v,label)
local title = mw.title.new(clean(v))
if not title then return text(label or v) end
return '[[:' .. title.prefixedText .. '|' .. text(label or v) .. ']]'
end
local function canonical(v)
local t = mw.title.new(clean(v))
for i=1,5 do
if not t or not t.isRedirect or not t.redirectTarget then break end
t=t.redirectTarget
end
return t and t.prefixedText or clean(v)
end
function p.canonicalPerson(frame) return canonical(frame.args[1]) end
local function date(v)
v=clean(v)
if v=='' or v:match('^%d%d%d%d$') then return text(v) end
local ok,result=pcall(function() return mw.getContentLanguage():formatDate('j xg Y',v) end)
return text(ok and result or v)
end
local function query(tables,fields,args)
args=args or {}; args.limit=500
local rows,offset={},0
repeat
args.offset=offset
local batch=mw.ext.cargo.query(tables,fields,args)
for _,r in ipairs(batch) do rows[#rows+1]=r end
if #batch<500 then return rows end
offset=offset+500
until offset>=10000
error('Список слишком велик; требуется постраничный просмотр.')
end
local function groups()
if not groupCache then
groupCache=query('Collective','_pageName=page,Name,Type,OtherNames,Description,Test', {orderBy='_pageID'})
end
return groupCache
end
local function aliases(row)
local out,seen={},{}
for _,name in ipairs({row.page or '', row.Name or '', unpack(split(row.OtherNames))}) do
if name~='' and not seen[name] then out[#out+1]=name;seen[name]=true end
end
return out
end
local function findGroup(name)
local key=mw.ustring.lower(clean(name):gsub('_',' '))
for _,g in ipairs(groups()) do
for _,a in ipairs(aliases(g)) do
if mw.ustring.lower(a:gsub('_',' '))==key then return g end
end
end
end
local function linkedList(value,sep)
local out={};for _,v in ipairs(split(value,sep)) do out[#out+1]=link(v) end
return table.concat(out,'<br />')
end
function p.card(frame)
local a=frame:getParent().args
local box=mw.html.create('table'):addClass('infobox bw-collective-card')
box:tag('tr'):tag('th'):attr('colspan',2):wikitext(text(a.Name or mw.title.getCurrentTitle().text))
local function row(label,value)
if value and value~='' then
local tr=box:tag('tr');tr:tag('th'):wikitext(label);tr:tag('td'):wikitext(value)
end
end
if clean(a.Image)~='' then
local file=mw.title.new(a.Image,6)
if file and file.namespace==6 then
box:tag('tr'):tag('td'):attr('colspan',2):addClass('bw-collective-photo'):wikitext('[['..file.prefixedText..'|frameless|280px|'..text(a.ImageDes)..']]<br />'..text(a.ImageDes))
end
end
row('Тип',text(a.Type));row('Полное название',text(a.FullName))
row('Другие названия',text(a.OtherNames));row('Основан',date(a.Founded))
row('Завершение деятельности',date(a.Ended));row('Место создания',linkedList(a.PlaceFounded))
row('Страны',linkedList(a.Country));row('Города',linkedList(a.City))
row('Жанры',text(a.Genre));row('Основатели',linkedList(a.Founders))
row('Клубы и объединения',linkedList(a.Clubs));row('Официальные ссылки',a.Websites)
row('Контакты',a.Contacts)
return tostring(box)..'\n\n'..text(a.Description)..'\n'
end
-- Separate #invoke calls do not share Lua globals. Variables keeps this flag
-- within one page parse, including PageForms previews with repeated templates.
local function headingOnce(frame,key,heading)
if frame:callParserFunction('#var',{key})=='1' then return '' end
frame:callParserFunction('#vardefine',{key,'1'})
return '\n== '..heading..' ==\n'
end
function p.member(frame)
local a=frame:getParent().args
if clean(a.Person)=='' then return '' end
local out=headingOnce(frame,'bw-collective-member-heading','Состав')
local status=clean(a.Status)
if clean(a.EndDate)~='' then status='Бывший' end
if status=='Не уточнено' or status=='Действующий' then status='' end
local period=''
if clean(a.StartDate)~='' then period='с '..date(a.StartDate) end
if clean(a.EndDate)~='' then period=period..' по '..date(a.EndDate) end
local item=mw.html.create('div'):addClass('bw-collective-member')
local target=canonical(a.Person)
local person=mw.ext.cargo.query('Persona','Name',{where='_pageName='..quote(target),limit=1})
local label=person[1] and clean(person[1].Name) or ''
if label=='' then label=a.Person end
local memberTitle=mw.title.new(target)
local memberLink=link(target,label)
if memberTitle and memberTitle.namespace==0 and memberTitle.interwiki=='' and not memberTitle.exists then
local first=target:match('^([^ ]+)') or target
local last=target:match('([^ ]+)$') or target
local full=target:find(',',1,true) and target or last..', '..first
local values='Persona[Name]='..mw.uri.encode(label,'PATH')..'&Persona[FullName]='..mw.uri.encode(full,'PATH')
local args={target=target,form='Persona',['query string']=values}
if mw.title.getCurrentTitle().namespace==0 then args[1]='create page' end
memberLink=frame:callParserFunction('#formredlink',args)
end
item:tag('strong'):wikitext(memberLink)
if clean(a.Role)~='' then item:wikitext(' — '..text(a.Role)) end
local details={}
if status~='' then details[#details+1]=text(status) end
if period~='' then details[#details+1]=period end
if #details>0 then
item:tag('span'):addClass('bw-collective-period'):wikitext(' · '..table.concat(details,' · '))
end
if clean(a.Note)~='' then item:tag('div'):addClass('bw-collective-note'):wikitext(text(a.Note)) end
return out..tostring(item)..'\n'
end
function p.period(frame)
local a=frame:getParent().args
if clean(a.StartDate)=='' and clean(a.EndDate)=='' and clean(a.Note)=='' then return '' end
local out=headingOnce(frame,'bw-collective-period-heading','Периоды деятельности')
local value=clean(a.StartDate)~='' and date(a.StartDate) or 'Начало не уточнено'
value=value..(clean(a.EndDate)~='' and ' — '..date(a.EndDate) or '')
if clean(a.Note)~='' then value=value..' — '..text(a.Note) end
return out..'<div class="bw-collective-period-row">'..value..'</div>\n'
end
function p.personGroups(frame)
local a=frame.args
local page=canonical(a.page or mw.title.getCurrentTitle().prefixedText)
local terms={page};if clean(a.name)~='' then terms[#terms+1]=a.name end
local conditions={};for _,n in ipairs(terms) do conditions[#conditions+1]='Person='..quote(n) end
local found=query('CollectiveMember','_pageName=page', {where=table.concat(conditions,' OR '),orderBy='_pageID'})
local out,seen={},{}
local function add(name)
local g=findGroup(name); local target=g and g.page or name
if g and g.Test=='1' then return end
if not seen[target] then out[#out+1]=link(target,g and g.Name or name);seen[target]=true end
end
for _,r in ipairs(found) do add(r.page) end
for _,name in ipairs(split(a.legacy,',')) do add(name) end
return table.concat(out,'<br />')
end
function p.author(frame)
local name=clean(frame.args[1]);if name=='' then return '' end
local g=findGroup(name)
if g then return link(g.page,name) end
local title=mw.title.new(name)
if not title then return text(name) end
if title.exists then return link(name) end
local collective=false
for _,n in ipairs(split(frame.args[2],',')) do
if mw.ustring.lower(n)==mw.ustring.lower(name) then collective=true end
end
local form=collective and 'Collective' or 'Persona'
local values
-- PageForms' query decoder preserves literal plus signs. Encode spaces as
-- %20 rather than + so auto-created names keep their original spelling.
if collective then values='Collective[Name]='..mw.uri.encode(name,'PATH')..'&Collective[Type]='..mw.uri.encode('Коллектив','PATH')
else
local last=name:match('([^ ]+)$') or name
local first=name:match('^([^ ]+)') or name
values='Persona[Name]='..mw.uri.encode(name,'PATH')..'&Persona[FullName]='..mw.uri.encode(last..', '..first,'PATH')
end
return frame:callParserFunction('#formredlink',{target=name,form=form,['query string']=values,'create page'})
end
function p.performerValues()
local values,seen={},{}
local function add(n)
n=clean(n)
if n~='' and not n:find(',',1,true) and not seen[n] then values[#values+1]=n;seen[n]=true end
end
for _,r in ipairs(query('Persona','Name',{orderBy='_pageID'})) do add(r.Name) end
for _,g in ipairs(groups()) do if g.Test~='1' then add(g.Name) end end
table.sort(values)
return table.concat(values,',')
end
local function tableStart(headers)
local t=mw.html.create('table'):addClass('wikitable bw-collective-table')
local tr=t:tag('tr');for _,h in ipairs(headers) do tr:tag('th'):wikitext(h) end
return t
end
local function cell(tr,value) tr:tag('td'):wikitext(value or '') end
function p.repertoire(frame)
local page=mw.title.getCurrentTitle().prefixedText
local g=findGroup(page)
if not g then return '\n== Исполнения песен ==\nПока нет добавленных исполнений.\n' end
local where={}
for _,name in ipairs(aliases(g)) do where[#where+1]='p.performer HOLDS '..quote(name) end
local rows=query('Performance=p,song=s','p._ID=id,p._pageName=page,p.performer=performers,p.link=url,p.type=kind,p.comm=note,s.Name=name,s.Composer=composer,s.Lyric=lyric,s.FirstString=first',
{where='('..table.concat(where,' OR ')..')',['join']='p._pageID=s._pageID',orderBy='s.Name,p._ID'})
local out='\n== Исполнения песен ==\n'
if #rows==0 then out=out..'Пока нет добавленных исполнений. Их можно указать в разделе «Исполнение» на страницах песен.\n'
else
local t=tableStart({'Песня','Авторы','Первая строка','Запись','Исполнители / комментарий'})
local seen={}
for _,r in ipairs(rows) do
if not seen[r.id] then
seen[r.id]=true;local tr=t:tag('tr')
cell(tr,link(r.page,r.name or r.page))
cell(tr,text(r.composer==r.lyric and r.composer or table.concat({r.composer or '',r.lyric or ''},' / ')))
cell(tr,text(r.first))
local url=r.url or ''
cell(tr,url:match('^https?://') and not url:find('[%s<>%[%]{}|]') and '['..url..' '..text(r.kind or 'Запись')..']' or text(r.kind))
cell(tr,text(r.performers)..(clean(r.note)~='' and '<br />'..text(r.note) or ''))
end
end
out=out..'<div class="bw-collective-scroll">'..tostring(t)..'</div>\n'
end
where={}
for _,name in ipairs(aliases(g)) do
for _,field in ipairs({'Composer','Lyric','Translation'}) do where[#where+1]=field..' HOLDS '..quote(name) end
end
local songs=query('song','_pageName=page,Name,FirstString',{where=table.concat(where,' OR '),orderBy='Name,_pageID'})
if #songs>0 then
out=out..'\n== Авторские песни ==\n'
local t=tableStart({'Песня','Первая строка'});local seen={}
for _,r in ipairs(songs) do if not seen[r.page] then local tr=t:tag('tr');cell(tr,link(r.page,r.Name));cell(tr,text(r.FirstString));seen[r.page]=true end end
out=out..'<div class="bw-collective-scroll">'..tostring(t)..'</div>\n'
end
return out
end
function p.catalog()
local out={};local buckets={}
for _,g in ipairs(groups()) do
if g.Test~='1' then local kind=clean(g.Type);if kind=='' then kind='Коллектив' end
buckets[kind]=buckets[kind] or {};table.insert(buckets[kind],g)
end
end
for _,kind in ipairs({'Дуэт','Трио','Ансамбль','Группа','Творческий проект','Коллектив'}) do
if buckets[kind] then
out[#out+1]='\n== '..kind..' ==\n'
table.sort(buckets[kind],function(a,b)return a.Name<b.Name end)
for _,g in ipairs(buckets[kind]) do out[#out+1]='* '..link(g.page,g.Name)..(clean(g.Description)~='' and ' — '..text(g.Description) or '')..'\n' end
end
end
return #out>0 and table.concat(out) or 'Пока нет заполненных страниц коллективов.'
end
return p