Ballot/old
- 1 year ago
- 0
- 0
Модуль для шаблона
{{
Результат выборов арбитров
}}
Находящийся в разработке шаблон для отображения результатов выборов inwiki, todo:
Псст… не хочешь глянуть в предпросмотре на
{{#invoke:Ballot|open_vote|2=json}}
?
local p = {}
local trim = mw.text.trim
local green90, green85, green80, green75, green70 = "#d5fdf4", "#d3fcf3", "#b6f4e7", "#9aebd9", "#7fe3cd"
local red90, red85, red80, red75, red70 = "#fee7e6", "#ffe9e8", "#fbd3d2", "#f8bfbd", "#f4a8a6"
local yellow90, yellow85, yellow80, yellow75, yellow70 = "#fef6e7", "#fff7e8", "#fff1d4", "#ffeac0", "#ffe5ac"
--[[
Процесс работы:
1) Устанавливается список кандидатов
1.1) Список кандидатов ищется на разных страницах
1.2) В случае ошибок сообщения о них передаются в таблице и потом группируются
2) Устанавливается список избирателей
2.1) нужна заранее подготовленная таблица к выборам, без неё нет автоматической проверки критериев
2.1.1) TODO - если вообще нет такой таблицы, пропускать всех так и писать ошибку
2.2) принимаются команды с комментариями от бюрократов о необходимости учесть кого-то
BUG - необходимо переписать, чтобы была возможность указать по одному пользователю много разных частичных ограничений
3) Установление списка голосов
3.1) vote_listing, грабит страницы с голосами при помощи pooling
3.2) compute переписывает голоса в таблицу допущенных, добавляя в таблицу err списки исключённых
3.3) сортировка кандидатов согласно ВП:ВАК
TODO - сделать строку серой, пока не перейдена граница 20 голосов
3.4) формируется итоговая таблица
3.4.1) записывается заранее заданная шапка
3.4.2) на основании valid_votes функция line_format записывает строки
3.4.3) сообщения об ошибках группируются и записываются самые важные вместе с подвалом таблицы
TODO - необходимо компактно отображать ещё больше информации
3.5) выдаётся итоговый результат
Таблицы
candidatrue[candidate] - (ключ - ник кандидата, значение true/nil в зависимости от того есть ли о нём запись)
candidates[N] - (без ключа, содержит ники кандидатов)
err[N] - (без ключа, {тип ошибки текстом; группа ошибки; серьёзность ошибки: 1 - критическая, 2 - серьёзная, 3 - мелкая, 4 - не ошибка; текст ошибки})
vote_table[candidate] = votes[voter] {{nick,bool_pro,bool_contra,time}, }
exceptions[user] = {is_exception,in_general,allow,prevent,comment,candidate} --старая форма
exceptions[user] = { {is_exception,in_general,allow,prevent,comment,candidate}, ...}
--]]
-- local mw = mw or {} -- для уменьшения количества ошибок от локального дебаггера
-- local frame -- frame из p.open_vote, чтобы не волноваться о доступе из любой функции
-- вызов функцией не переданного в неё аргумента приводит к [[Замыкание (программирование)|замыканию]], которое тут не нужно
local monthlang = {"января","февраля","марта","апреля","мая","июня","июля","августа","сентября","октября","ноября","декабря"}
local month_to_num = {["января"]=1,["февраля"]=2,["марта"]=3,["апреля"]=4,["мая"]=5,["июня"]=6,
["июля"]=7,["августа"]=8,["сентября"]=9,["октября"]=10,["ноября"]=11,["декабря"]=12,["-"]=""}
local table_start, table_end = '<table class="wikitable sortable" width=33%><tr style="text-align:center"><th class="headerSortDown">#</th><th>Кандидат</th><th>+</th><th>−</th><th>Δ</th><th>Σ</th><th>%</th></tr>', "</table>"
local err_start, err_end = '<tr class="sortbottom" style="text-align:center"><td colspan=7><table class="mw-collapsible mw-collapsed"><tr><th>Дополнительная информация </th></tr><tr><td>','</td></tr></table></td></tr>'
local err_type = {["no data"] ="Нет данных:", ["clash"] ="Расхождения:", ["vot_ers"] ="Исключения:"}
-- функция для проверки, содержит ли массив запрашиваемое значение
local function is_in_list ( var, list )
for i=1, #list do
if var == list[i] then
return true
end
end
return false
end
-- функция для подсчёта элементов в массиве
local function n_list (tab)
if type(tab) ~= "table" then
return 0
end
local i = 0
for k,v in pairs(tab) do
i = i + 1
end
return i
end
-- граббинг данных со страниц с голосами
local function pooling (content, plus, votes)
if not content or #content < 2 then return end
votes = votes or {}
for line in string.gmatch(content, "[^\n]+") do
--при моментальном переголосовании в течении минуты модуль не может определить позднейший голос
local user, nick, h, m, d, mon, y = string.match(line, "^#%s%[%[user:(.-)|(.-)%]]%s(%d%d):(%d%d),%s(.-)%s(.-)%s(.-)%s%(UTC%)")
if user then
local date = os.time{year=y, month=month_to_num[mon], day=d, hour=h, min=m}
votes[user] = votes[user] or {}
table.insert(votes[user], {
nick, -- user == voter == nick
plus, -- pro
not plus, -- contra
date}) -- date
end
end
return votes
end
-- =p.dress("Кандидат",true,false,"причина")
local function dress (candidate,pro,contra,comm)
return "<span style='font-size:90%' class='ts-comment-commentedText' title='" .. candidate .. (comm and " (" or "") .. (comm or "") .. (comm and ")" or "") .. "'>" .. (pro and "+" or (contra and "−" or "?")) .. "</span>"
end
-- TODO подсчёт результатов на заданную дату
-- отмены голосов бюрократами будут вневременные и не повлияют на динамику
local function compute (err,vote_table,electoratrue,exceptions,has_page,date)
if not vote_table then return nil end
date = date or os.time()
local valid_votes = {}
for candidate, votes in pairs(vote_table) do
valid_votes[candidate] = valid_votes[candidate] or {}
-- mw.log("== " .. candidate .. " ==")
for voter, vote in pairs(votes) do
err[votes] = err[votes] or {}
err[votes][candidate] = err[votes][candidate] or {}
err[votes][candidate]["change"] = #vote - 1
valid_votes[candidate][voter] = valid_votes[candidate][voter] or {}
local min_date, pro, contra = 0, false, false
-- local max_date = 17179869184
table.sort(vote,function(a,b) return a[4]>b[4] end)
pro, contra, timestamp = vote[1][2], vote[1][3], vote[1][4]
if #vote ~= 1 then
table.insert(err,{"vot_ers",voter,"<small><i>change: </i></small>",dress(candidate,false,false,#vote)})
end
--[[ for i,subvote in ipairs(vote) do
-- subvote[N] {nick,bool_pro,bool_contra,time}
if subvote[1] ~= voter then
table.insert(err,{"clash",candidate,voter,"(" .. subvote[1] .. ")"})
break
end
if #vote == 1 then
pro, contra = subvote[2], subvote[3]
elseif subvote[4] > min_date then
table.insert(err,{"vot_ers",voter,"<small><i>change: </i></small>",dress(candidate,false,false,#vote)})
pro, contra, v_date, change = subvote[2], subvote[3], subvote[4], true
end
end
--]]
if has_page and not electoratrue[voter] then
table.insert(err,{"vot_ers",voter,"<small><i>activity: </i></small>",dress(candidate,pro,contra)})
end
valid_votes[candidate][voter] = {electoratrue[voter] or not has_page or false, pro, contra, timestamp}
if exceptions[voter] and type(exceptions[voter]) == "table" then
for _, ex in ipairs(exceptions[voter]) do
-- убрал проверку ex[1], т.к. он пока всегда true
if ex[2] or ex[7] == candidate then
if ex[3] then
valid_votes[candidate][voter][1] = true
table.insert(err,{"vot_ers",voter,"<small><i>allowed: </i></small>",dress(candidate,pro,contra,ex[5] and ex[6] or nil)})
elseif ex[4] then
-- mw.log(voter .. " - " .. candidate)
valid_votes[candidate][voter][1] = false
table.insert(err,{"vot_ers",voter,"<small><i>restricted: </i></small>",dress(candidate,pro,contra,ex[5] and ex[6] or nil)})
end
end
end
end
-- exceptions[voter] {{is_exception,in_general,allow,prevent,bool_comment,comment,candidate}, ...}
-- err[N] {txt_type; txt_group; txt_subgroup; txt_comment}
-- valid_votes[candidate][voter]{[1]= bool_valid, [2]=bool_pro, [3]=bool_contra}
end
end
return err, valid_votes
end
local function reform (err, valid_votes)
local pre_result, pre_sort = {},{}
for candidate, votes in pairs(valid_votes) do
local count_sup,count_opp = 0,0
for voter, vote in pairs(votes) do
if vote[1] and vote[2] then
count_sup = count_sup + 1
elseif vote[1] and vote[3] then
count_opp = count_opp + 1
else
-- mw.log(candidate .. ">" .. voter .. ">("..tostring(vote[1])..","..tostring(vote[2])..","..tostring(vote[3])..")")
end
end
local count_tot = count_sup + count_opp
local percent = count_tot == 0 and 0 or (count_sup * 100 / count_tot)
pre_result[candidate] = {count_sup,count_opp,count_tot,percent}
-- сортировка по голосам за, но те кто набирает процент - выше
local sort_index
-- mw.log(candidate .. " - " .. percent .. "(" .. tostring(percent > (200/3)).. ")")
if percent >= (200/3) then
sort_index = 1000000 + 1000 * count_sup - count_opp
else
sort_index = 100 * (100/3 + percent) + count_sup
end
-- mw.log(sort_index)
table.insert(pre_sort,{candidate, sort_index})
end
-- mw.logObject(pre_sort)
table.sort(pre_sort, function(a,b) return (a[2] == b[2]) and (a[1] < b[1]) or (a[2]>b[2]) end)
return err, pre_result, pre_sort
end
-- valid_votes[candidate][voter]{[1]= bool_valid, [2]=bool_pro, [3]=bool_contra}
-- line_format(i, cand[1], pre_result[cand[1]], arb_page))
local function line_format (i, candidate, c_res, arb_page)
local count_sup,count_opp,count_tot,percent = c_res[1], c_res[2], c_res[3], c_res[4]
local passing = percent > 66.66
local second = percent >= 50
-- нужны даты голосования и автоматический вывод то одних, то других ссылок
local main_arb_page = string.gsub( arb_page, "%/Голосование", "")
local isNew = mw.title.new(arb_page.."/Голоса"):getContent(), pathname
if isNew then pathname = "/Голоса" else pathname = "/*" end
local tr = mw.html.create( 'tr' )
tr :css( 'background', passing and '#cfc;' or (second and '#ffc;' or '#fcc;') )
:tag( 'td' ):css('text-align','right'):wikitext( (passing and '' or '<small>') .. i .. (passing and '' or '</small>')):done()
:tag( 'td' ):css('text-align','center'):css('white-space','nowrap'):wikitext( table.concat{"[[".. arb_page .. pathname .."#", candidate, '|', candidate, "]] ([[", main_arb_page,"#", candidate,"|обс.]])"}):done()
:tag( 'td' ):css('text-align','right'):wikitext( count_sup ):done()
:tag( 'td' ):css('text-align','right'):wikitext( count_opp ):done()
:tag( 'td' ):css('text-align','right'):wikitext( count_sup-count_opp*2 ):done()
:tag( 'td' ):css('text-align','right'):wikitext( count_tot ):done()
:tag( 'td' ):css('text-align','right'):wikitext( (string.gsub( mw.ustring.format("%.2f %%", percent), "%.", ","))):done()
return tostring( tr )
end
local function vote_listing (err,vote_table,arb_page,candidate)
local pattern = "\n#[^#*:][^\n]+"; -- подсчёт нумерованных списков
local pagepointer_sup=mw.title.new(arb_page .. '/+/' .. candidate, '')
local pagepointer_opp=mw.title.new(arb_page .. '/-/' .. candidate, '')
local text_sup=pagepointer_sup.getContent(pagepointer_sup)
local text_opp=pagepointer_opp.getContent(pagepointer_opp)
local votes = {}
votes = pooling(text_sup, true, votes)
local pro_votes = n_list(votes)
-- mw.log(candidate .. " + " .. pro_votes)
votes = pooling(text_opp, false, votes)
local opp_votes = 1+ n_list(votes) - pro_votes
-- mw.log(candidate .. " - " .. opp_votes)
local votes = votes or {} -- хак для отображения пустой таблицы
if not votes then return err, vote_table end
vote_table[candidate] = votes
err[votes] = err[votes] or {}
err[votes][candidate] = err[votes][candidate] or {}
err[votes][candidate]["raw_pro"] = pro_votes
err[votes][candidate]["raw_opp"] = opp_votes
return err, vote_table
end
-- для сложения таблиц кандидатов
local vlist={}
function vlist.__add (tru1,tru2)
if not tru1 or not tru2
or type(tru1) ~= "table" or type(tru2) ~= "table"
then return end
local tru_list = {}
for key,bool in pairs(tru1) do
tru_list[key] = bool
end
for key,bool in pairs(tru2) do
tru_list[key] = bool
end
return tru_list
end
local nomination = function(page)
local nomination_page_text = page:getContent() or ""
local block_pattern = "%{%{ВАРБ:строка|кандидат=([^\n]*)\n|номинатор=([^\n]*)\n|согласие=([^\n]*)\n|отказ=([^\n]*)\n|бюрократ=([^\n]*)\n|отказ бюрократа=([^\n]*)\n%}%}"
local old_block_pattern = "%{%{ВАРБ:старая строка|1=([^\n]*)|2=([^\n]*)|3=([^\n]*)|4=([^\n]*)|5=([^\n]*)%}%}"
local candidates, candidatrue = {}, {}
setmetatable(candidatrue,vlist)
for candidate, submitter, consent, refusal, admission, non_admission in nomination_page_text:gmatch(block_pattern) do
candidate = trim(candidate)
if consent:find("(UTC)") and admission:find("(UTC)") then
candidatrue[candidate] = true
table.insert(candidates, candidate)
end
end
for consent_type, nominee, nominator, consent, admission in nomination_page_text:gmatch(old_block_pattern) do
nominee = trim(nominee)
if consent_type=="+" and admission:find("(UTC)") then
candidatrue[nominee] = true
table.insert(candidates, nominee)
end
end
return candidates, candidatrue
end
local function line_processor(raw_text_candid,patt_string,patt_candid,patt_candid_end,position)
local candidates, candidatrue = {}, {}
setmetatable(candidatrue,vlist)
for line in raw_text_candid:gmatch("[^\n]+") do
if string.match( line, patt_string ) then
local candidate_text = string.match( line, patt_candid)
local pos0, pos1 = string.find(candidate_text,patt_candid_end)
local candidate = string.sub(candidate_text, position, pos0 - 1)
if candidate ~= "" and candidatrue[candidate] ~= true then
candidatrue[candidate] = true
table.insert(candidates, candidate)
end
end
end
return candidates, candidatrue
end
-- =p.caret({},"Википедия:Выборы арбитров/Лето 2021/Голосование/Исключения")
local caret = function (err, page)
local raw_text = mw.title.new(page):getContent() or ""
if #raw_text < 2 then
table.insert(err,{"no data","bureaucrat panel","",'[['.. page ..'|/Исключения]]'})
return err, {}
end
local content, exceptions = false, {}
for line in raw_text:gmatch("[^\n]+") do
if content then
-- TODO exceptions[voter] {{is_exception,in_general,allow,prevent,bool_comment,comment,candidate}, ... }
-- "*" true true true false ""
-- "*" true false false true "отказался" "Candid"
local line_data = string.match( line, "%*%s(.+)")
local _,n = line_data:gsub("|","")
if n == 0 then
local voter_nick = mw.text.trim(line_data)
-- mw.log("0 " .. voter_nick)
exceptions[voter_nick] = exceptions[voter_nick] or {}
table.insert(exceptions[voter_nick],{true,true,true,false})
elseif n == 1 then
local voter, comment = mw.ustring.match(line_data,"([^|]+)|([^|]+)")
voter = mw.text.trim(voter)
comment = mw.text.trim(comment)
-- mw.log("1 " .. voter .. " - " .. comment)
exceptions[voter] = exceptions[voter] or {}
table.insert(exceptions[voter],{true,true,false,true,true,comment})
elseif n == 2 then
local voter, candidate, comment = mw.ustring.match(line_data,"([^|]+)%s?|%s?([^|]+)|([^|]+)")
voter = mw.text.trim(voter)
candidate = mw.text.trim(candidate)
comment = mw.text.trim(comment)
-- mw.log("1 " .. voter .. " - " .. comment .. " - " .. candidate)
exceptions[voter] = exceptions[voter] or {}
table.insert(exceptions[voter],{true,false,false,true,true,comment,candidate})
else
table.insert(err,{"clash","bureaucrat panel","",line_data})
end
elseif string.match( line, ".*</noinclude>" ) then
content = true
end
end
-- mw.logObject(exceptions)
return err, exceptions
end
-- =p.exclamat(mw.title.new("Википедия:Выборы арбитров/Лето 2021/Голосование/!"))
local function exclamat (page)
return line_processor(page:getContent() or "","====.+}}","====.+}}","{{",8)
end
-- =p.ampersan(mw.title.new("Википедия:Выборы арбитров/Лето 2021/Голосование/&"))
local function ampersan (page)
return line_processor(page:getContent() or "","%|[^|]+%|%|%[%[.*","%|[^|]+%|%|%[%[","||%[%[",2)
end
-- =p.allvotes(mw.title.new("Википедия:Выборы арбитров/Лето 2021/Голосование/Голоса"))
local function allvotes (page)
return line_processor(page:getContent() or "","%*%s%[%[#[^|]+|[^%]]+%]%]","%*%s%[%[#[^|]+|[^%]]+%]%]","|",6)
end
-- =p.w_quarry(mw.title.new("Википедия:Выборы арбитров/Лето 2021/Избиратели"))
local function w_quarry (page)
local electorate, electoratrue = line_processor(page:getContent() or "","|[^|]+||%d+||%d+||%d+","^|[^|]+||","||",2)
if not electorate[1] then
local raw_text = page:getContent()
-- TODO сообщение об ошибке
if not raw_text then return {}, {} end
for line in raw_text:gmatch("[^\n]+") do
if line ~= "" and electoratrue[line] ~= true then
electoratrue[line] = true
table.insert(electorate, line)
end
end
end
-- mw.logObject(electoratrue)
-- mw.logObject(electorate)
return electorate, electoratrue
end
-- =p.elec_listing({},"Википедия:Выборы арбитров/Лето 2021/Голосование")
-- =p.elec_listing({},"Шаблон:Результат выборов арбитров")
local function elec_listing (err, arb_page)
local page_1, page_2, _ = mw.ustring.match(arb_page,"([^/]+)/([^/]+)/([^/]+)")
local page_name, has_page
if not page_1 then
page_name = table.concat({page_1 or arb_page,"Избиратели"},"/")
else
page_name = table.concat({page_1,page_2,"Избиратели"},"/")
end
local page = mw.title.new(page_name)
local electorate, electoratrue = w_quarry (page)
if not electorate[1] then
table.insert(err,{"no data","elec","","[[" .. page_name .. "|/Избиратели]]"})
has_page = false
else
has_page = true
end
return err, electoratrue, has_page
end
local function merge(candidates,err,candidatrue,mark,candidates_merge)
for key,bool in pairs(candidatrue) do
if not is_in_list(key,candidates_merge) then
table.insert(err,{"no data",mark,"",key})
elseif not is_in_list(key,candidates) then
table.insert(candidates,key)
end
end
return candidates,err
end
-- =p.cand_listing("","Википедия:Выборы арбитров/Лето 2021/Голосование")
local function cand_listing (err, arb_page)
local candidates, candidatrue = {}, {}
local nom_page = string.gsub( arb_page, "%/Голосование", "/Выдвижение")
-- local pagep_candid_excl = mw.title.new(arb_page .. '/!')
-- local pagep_candid_ampe = mw.title.new(arb_page .. '/&')
local pagep_candid_allvotes = mw.title.new(arb_page .. '/Голоса')
local pagep_candid_nom = mw.title.new(nom_page)
-- local candidates_excl, candidatrue_excl = exclamat(pagep_candid_excl)
-- local candidates_ampe, candidatrue_ampe = ampersan(pagep_candid_ampe)
local candidates_allvotes, candidatrue_allvotes = allvotes(pagep_candid_allvotes)
local candidates_nom, candidatrue_nom = nomination(pagep_candid_nom)
-- if not candidates_excl[1] then
-- table.insert(err,{"no data","service page","",'[['.. tostring(pagep_candid_excl) ..'|/!]]'}) end
-- if not candidates_ampe[1] then
-- table.insert(err,{"no data","allpages changes","",'[['.. tostring(pagep_candid_ampe) ..'|/&]]'}) end
-- if not candidates_allvotes[1] then
-- table.insert(err,{"no data","all votes","",'[['.. tostring(pagep_candid_allvotes) ..'|/Голоса]]'}) end
if not candidates_nom[1] then
table.insert(err,{"no data","nomination","",'[['.. tostring(pagep_candid_nom) ..'|/Выдвижение]]'}) end
candidatrue = candidatrue_allvotes + candidatrue_nom
local err_spec = {}
-- candidates,err_spec = merge(candidates,err_spec,candidatrue,"service page",candidates_excl)
-- candidates,err_spec = merge(candidates,err_spec,candidatrue,"allpages changes",candidates_ampe)
candidates,err_spec = merge(candidates,err_spec,candidatrue,"all votes",candidates_allvotes)
candidates,err_spec = merge(candidates,err_spec,candidatrue,"all votes",candidates_nom)
-- TODO нужна более чёткая проверка наличия расхождений
if not candidates[1] then
for _, er in ipairs(err_spec) do
table.insert(err,er)
end
end
return err, candidates
end
-- todo - сообщения об ошибках, исключение голосов
-- функция для работы через {{Результат выборов арбитров}}
-- =p.open_vote(mw.getCurrentFrame():newChild{title="Википедия:Выборы арбитров/Лето 2021/Голосование",args={"Википедия:Выборы арбитров/Лето 2021/Голосование"}})
function p.open_vote(frame)
local parent = frame:getParent()
local args = parent.args
local ch_args = frame.args --для отладки
local arb_page = ch_args[1] or mw.title.getCurrentTitle().fullText
local return_json = (ch_args[2] == "json")
-- модуль работает на старых страницах, путь приводится к одному формату таким образом
arb_page = string.gsub( arb_page, "%/Голосование%/Предитоги", "/Голосование")
arb_page = string.gsub( arb_page, "%/Голосование%/Предытоги", "/Голосование")
arb_page = string.gsub( arb_page, "%/Форум", "/Голосование")
arb_page = string.gsub( arb_page, "%d%d%d%d ?%d?$", "%0/Голосование")
local err, vote_table = {}, {}
local candidates, electoratrue, exceptions, valid_votes, pre_result, pre_sort
err, candidates = cand_listing (err, arb_page)
err, electoratrue, has_page = elec_listing (err, arb_page)
err, exceptions = caret(err, arb_page .. "/Исключения")
local result = {}
table.insert(result, table_start)
if #candidates > 0 then
for _, candidate in ipairs(candidates) do
err, vote_table = vote_listing (err,vote_table,arb_page,candidate)
end
-- TODO function under construction
err, valid_votes = compute (err,vote_table,electoratrue,exceptions,has_page) --,date
-- mw.logObject(err)
if return_json then
return mw.text.jsonEncode(valid_votes)
end
err, pre_result, pre_sort = reform (err, valid_votes)
-- TODO новая функция - недоделки
for i, cand in ipairs(pre_sort) do
table.insert(result, line_format(i, cand[1], pre_result[cand[1]], arb_page))
end
--mw.logObject(pre_result)
--]]
else
table.insert(err,{"no data","elec","","Отсутствуют кандидаты"})
end
local err_notice = {}
for i, err in ipairs(err) do
err_notice[err[1]] = err_notice[err[1]] or {} -- err type
err_notice[err[1]][err[2]] = err_notice[err[1]][err[2]] or {} -- err group
err_notice[err[1]][err[2]][err[3]] = err_notice[err[1]][err[2]][err[3]] or {} -- err subgroup
table.insert(err_notice[err[1]][err[2]][err[3]],err[4]) -- err messaage
end
local err_mass = {}
for e_type, gr_errs in pairs(err_notice) do
table.insert(err_mass,err_type[e_type])
for e_group, subgr_errs in pairs(gr_errs) do
table.insert(err_mass,e_group)
for e_subgr,e_msgs in pairs(subgr_errs) do
table.insert(err_mass,e_subgr)
for i, e_msg in ipairs(e_msgs) do
table.insert(err_mass,e_msg)
end
end
end
end
-- mw.logObject(err_mass)
if type(err_mass) == "table" and err_mass[1] then
table.insert(err_mass,1,err_start)
table.insert(err_mass,err_end)
table.insert(result,table.concat(err_mass," "))
end
table.insert(result, table_end)
return table.concat(result)
end
return p