Создание надстроек для офисного пакета «МойОфис». Часть 2. Расширяем структуру файлов надстройки и удалённая отладка

Все статьи цикла:

  1. Введение

  2. Расширяем структуру файлов надстройки и удалённая отладка (эта статья)

Итак, напомню предисторию создания статей. В связи с тем, что разработчики отечественного офисного пакета «МойОФис», не позаботились о создании хоть какой-нибудь встроенной среды разработки для надстроек (расширенных макросов на языке Lua), то я решил попробовать сам создать такую среду на базе программы LuaRT под Windows. После создания первой надстройки, о чем можно почитать в первой, вводной части данного цикла статей, следующим шагом у меня возникла идея, как бы представить весь код, в форме более удобной для дальней работы? Хотелось бы, чтобы весь код не был свален в одном месте, а разделён по разным файлам.

Замечание по поводу своего уровня программирования на Lua

Здесь я хочу вставить небольшую ремарку: до этого момента я вообще никак не сталкивался с языком Lua (разве что знал о его существовании), и поэтому для меня многие моменты написания в нём не очевидны, так как я всё-таки больше программист по классическим ЯП (С++ и Java), хоть в последние годы VBA меня немного и «испортило». Поэтому, я могу иногда в коде допускать глупые с точки зрения опытного разработчика на Lua просчёты, за что прошу прощения. Сейчас я больше ставлю целью облегчить жизнь таким-же как я, а не показать свои навыки в Lua.

Вкратце повторю здесь исходный код простейшей надстройки. В ней в меню для надстроек «МойОфис» задаётся всего одна команда »Показать форму» (функция Actions.getCommands ()), при нажатии на которую откроется простейшая форма надстройки, которая пока ничего не делает с самим документом (функция Actions.ShowControls (context)). Состоит надстройка в общем то из одного файла entryform.lua, который расположен внутри папки /cmd, расположенной внутри корневой папки проекта надстройки.

Содержимое файла entryform.lua

Actions = {}
function Actions.getCommands()
	return {
			{
				id = "DlgForm.ShowDlg",
				menuItem = "Показать форму",
				command = Actions.ShowControls
			}
	}   
end
--Создадим набор из двух кнопок Ок и Cancel
Actions.dialogButtons = ui:DialogButtons{}
Actions.dialogButtons:addButton("OK", Forms.DialogButtonRole_Accept)
Actions.dialogButtons:addButton("Cancel", Forms.DialogButtonRole_Reject)

--Создадим метку по середине формы
Actions.lblMenu = ui:Label{
  Text="Поздравляю с первой надстройкой!", 
  Color=Forms.Color(255,0,0),  
}
Actions.lblMenu:setColor(Forms.Color(255,0,0))
Actions.lblMenu:setAlignment(Forms.Alignment_MiddleCenter)

--Создадим форму с созданными ранее контролами 
Actions.dlgBegin = ui:Dialog {
	Title = "Демонстрация Демонстрация первого расширения",
	Size = Forms.Size(600,300),
	Buttons = Actions.dialogButtons,
	--Позиционирование метки строго по центру формы
  ui:Column {		
		ui:Row {Actions.lblMenu},   
	}
} 
function Actions.ShowControls(context)	    
  context.showDialog(Actions.dlgBegin) 
end
return Actions

Если делать что то реально полезное, то писать весь код в одном файле наверное слишком громоздко и неудобно, и мне показалось весьма логичным, что стоит разделить функционал запуска надстройки и логику работы самой  надстройки. Иначе говоря, в entryform.lua оставить только то, что относится к командам, отображаемым в меню надстроек, а действия самих команд вынести в отдельную логику. Для этого в папке /cmd я создал файл forms.lua и перенес туда код относящийся к созданию  форм из предыдущего листинга.

Содержимое файла entryform.lua после разбиения на части

Actions = {}
function Actions.getCommands()
	return {
			{
				id = "DlgForm.ShowDlg",
				menuItem = "Показать форму",
				command = Actions.ShowControls
			}
	}   
end

function Actions.ShowControls(context)	    
  local Frm=dofile("/cmd/forms.lua")
  context.showDialog(Frm.dlgBegin) 
end

return Actions

Содержимое файла forms.lua

Frm={}
--Создадим набор из двух кнопок Ок и Cancel
Frm.dialogButtons = ui:DialogButtons{}
Frm.dialogButtons:addButton("OK", Forms.DialogButtonRole_Accept)
Frm.dialogButtons:addButton("Cancel", Forms.DialogButtonRole_Reject)

--Создадим метку по середине формы
Frm.lblMenu = ui:Label{
  Text="Поздравляю с первой надстройкой!", 
  Color=Forms.Color(255,0,0),  
}
Frm.lblMenu:setColor(Forms.Color(255,0,0))
Frm.lblMenu:setAlignment(Forms.Alignment_MiddleCenter)

--Создадим форму с созданными ранее контролами 
Frm.dlgBegin = ui:Dialog {
	Title = "Демонстрация первого расширения",
	Size = Forms.Size(600,300),
	Buttons = Frm.dialogButtons,
	--Позиционирование метки строго по центру формы
  ui:Column {		
		ui:Row {Frm.lblMenu},   
	}
} 
return Frm

Среда разработки LuaRT

Среда разработки LuaRT

Кратко поясню, кто мало понимает пока ещё Lua (как например я сам, поэтому наверное могу и быть в чем-то и ошибиться). Почти все структуры данных в Lua, исключая базовые типа чисел, представляют собой таблицы. Их внутреннее устройство не столь важно для понимания текущего вопроса (хотя, без понимания трудно будет вообще что-то написать, поэтому стоит разобраться на досуге), просто примите пока на веру. А так же есть метатаблицы, которые позволяют ко всему прочему в таблицах, ещё использовать и функции привязанные к ним, и выходит что-то похожее на объекты в ООП. Объявляются они по типу: Frm={}. К этой метатаблице мы можем дописывать свои данные (в том числе и локальные) и получать к ним доступ, через оператор ».». И как уже сказал, внутри таблицы можно использовать функции (доступ к ним реализуются потом через »:»). Предварительно описывать ни то, ни другое обычно нет необходимости (разве что для локальных переменных, через ключевое слово «local»). Когда всё что хотелось «сказать» в данной таблице описано, в конце файла со скриптом с помощью return возвращается описанный «объект» метатаблицы Frm. Далее, хотелось бы как то использовать файл forms.lua.  Тем, кто не в курсе как это делается обычно, сэкономлю время, и скажу, что загрузка кода в Lua возможно несколькими путями.

Создаём расширенную структуру файлов

Первый способ, по которому я сперва пошёл потому что он для меня был вроде бы самым очевидным, это  загрузка файла через стандартную для языка Lua  функцию dofile («путь_к_файлу»). Ну или, что наверное ещё более хорошо, так как есть контроль за ошибкой доступа в таком случае, через такую же стандартную для Lua функцию loadfile («путь_к_файлу»).  Так я и сделал, что видно из листинга entryform.lua. И если вы думаете, что приведенный код, с описанным способом будет работать, то «Ага, щазззз»!  Как оказалось, поскольку загружаться такой файл будет в Lua среде самого «МойОфис», то относительный путь, по типу /cmd/forms.lua в указанных функциях, не подходит, так как почему-то, путь к /cmd не суммируется с абсолютным путём к инсталлированному на жесткий диск расширению (напоминаю он будет  такого типа: C:\Users\Your_User_Name\AppData\Local\New Cloud Technologies Ltd\MyOffice\MyOffice Text\Extensions\Код_Надстройки). Не спрашивайте меня, почему он не прописан разработчиками в специальной переменной окружения скрипта. Я просто не знаю! Немного изучив руководство по созданию надстроек, я нашёл, что есть добавленная разработчиками для этих целей специальная системная функция openBundledFile (), и вроде бы она как раз служит для доступа к домашней папке текущего расширения! Но опять облом! Эта функция возвращает хэндл для POSIX File указателя, но по которому в Lua  не предусмотрен поиск пути к этому файлу! Иначе говоря, я могу получить сам файл, но не могу стандартными средствами, без инсталляции сторонних модулей, найти к нему путь. Как можно отыскать этот путь домашней к папке нашего расширения, мне не ответили даже в техподдержке самого «МойОфис» (правда, обещали что-то предпринять по этому поводу в будущем).

Итак, первый, очевидный, способ не сработал. Сперва я немного сник, но подумав немного над найденной информацией, понял очевидную вещь: у меня есть доступ к файлу, благодаря openBundledFile () . И я могу прочесть его содержимое в текстовую переменную. Задумался, а можно ли в Lua запустить текстовый блок на интерпретацию как код? Начал искать в интернете, и опа, оказалось что есть возможность запускать блоки кода (в терминах Lua — «chanck»), отдельной функцией loadstring (строковая_переменная). Хм, а вот и «очевидное» решение!

Второй способ: Найти нужный файл с помощью встроенной функции API «МойОфис» openBundledFile () и считать его содержимое. А потом что? Как оказалось (я опять же в Lua нуб), эта функция loadstring () возвращает функцию, которую надо запустить, и тогда она вернёт переменную-таблицу, в которой уже находятся все переменные и функции из forms.lua, которые нам нужны для запуска формы надстройки! Переписываю функцию запуска форм следующим образом (там немного сложнее, но я постарался откомментировать код для пояснений)

Изменённая функция ShowControls (context) файла entryform.lua по способу загрузки №2, через loadstring ()

function Actions.ShowControls(context)	  
  local formsFile=openBundledFile("/cmd/forms.lua")  -- уловка, позволяющая использовать дополнительный(ые) Lua файлы в проекте
  if formsFile~=nil then --Проверка что вызываемый файл существует   
    local outContent = formsFile:read "*a" -- прочесть содержимое файла
    formsFile:close()        
    local f, sError =loadstring(outContent) --загрузить содержимое как строковый буфер  
    if type(f) ~= 'function' then 
      return false, sError
    else      
      frm=f() --Получение объекта содержащего объекты диалоговых окон
      frm.context=context -- Передать контекст для открытия окон     
      context.showDialog(frm.dlgHello) --Запускаем первую форму, а остальная логика будет в форме 
    end     
  else
    EditorAPI.messageBox("No Exists forms.lua!") --Вывод сообщения, что файла с формами не существует в требуемом каталоге.
  end  
end
Новая структура надстройки

Новая структура надстройки

Аллилуйя! Можно работать дальше! Безусловно! Более того, такой способ оказался удивительно хорош тем, что все остальные методы, загружают все скрипты надстройки сразу при загрузке всего приложения. И если после этого вносить правки в код надстройки, то чтобы посмотреть результаты изменений, приходится полностью перезапускать приложение. А вот этот метод, за счёт динамической загрузки изменённого кода, позволил видеть изменения, сразу же! Достаточно только закрыть саму надстройку, и запустить её заново, что намного быстрее и проще, через рестарт всего приложения! А значит, можно было бы на этом и остановиться, но потом я занялся вопросами подключения удалённой отладки (куда же программисту без неё?!). И как оказалось, при отладке кода, загруженного таким способом нельзя использовать в нём точки остановки!  Точнее, расставить можно, но они не работали. Опять облом! Или нет? (спойлер!) Как выяснилось потом– нет! Есть способ и для такой загрузки! Но я на тот момент об этом ещё не знал, и посему пришлось искать третий способ загрузки (фэйспалм). Аллилуйя! Можно работать дальше! Безусловно! Более того, такой способ оказался удивительно хорош тем, что все остальные методы, загружают все скрипты надстройки сразу при загрузке всего приложения. И если после этого вносить правки в код надстройки, то чтобы посмотреть результаты изменений, приходится полностью перезапускать приложение. А вот этот метод, за счёт динамической загрузки изменённого кода, позволил видеть изменения, сразу же! Достаточно только закрыть саму надстройку, и запустить её заново, что намного быстрее и проще, через рестарт всего приложения! А значит, можно было бы на этом и остановиться, но потом я занялся вопросами подключения удалённой отладки (куда же программисту без неё?!). И как оказалось, при отладке кода, загруженного таким способом нельзя использовать в нём точки остановки!  Точнее, расставить можно, но они не работали. Опять облом! Или нет? (спойлер!) Как выяснилось потом– нет! Есть способ и для такой загрузки! Но я на тот момент об этом ещё не знал, и посему пришлось искать третий способ загрузки (фэйспалм). Аллилуйя! Можно работать дальше! Безусловно! Более того, такой способ оказался удивительно хорош тем, что все остальные методы, загружают все скрипты надстройки сразу при загрузке всего приложения. И если после этого вносить правки в код надстройки, то чтобы посмотреть результаты изменений, приходится полностью перезапускать приложение. А вот этот метод, за счёт динамической загрузки изменённого кода, позволил видеть изменения, сразу же! Достаточно только закрыть саму надстройку, и запустить её заново, что намного быстрее и проще, через рестарт всего приложения! А значит, можно было бы на этом и остановиться, но потом я занялся вопросами подключения удалённой отладки (куда же программисту без неё?!). И как оказалось, при отладке кода, загруженного таким способом нельзя использовать в нём точки остановки!  Точнее, расставить можно, но они не работали. Опять облом! Или нет? (спойлер!) Как выяснилось потом– нет! Есть способ и для такой загрузки! Но я на тот момент об этом ещё не знал, и посему пришлось искать третий способ загрузки (фэйспалм).

Аллилуйя! Можно работать дальше? Безусловно! Более того, такой способ оказался удивительно хорош тем, что все остальные методы, загружают все скрипты надстройки сразу при загрузке всего приложения «Мой офис». И если после этого вносить правки в код надстройки, то чтобы посмотреть результаты изменений, приходится полностью (!) перезапускать приложение. А вот этот метод, за счёт динамической загрузки изменённого кода, позволил видеть изменения, сразу же! Достаточно только закрыть саму надстройку, и запустить её заново из меню, что конечно быстрее и проще, через рестарт всего приложения! А значит, можно было бы на этом и остановиться, но потом я занялся вопросами подключения удалённой отладки (куда же программисту без неё?!). И как оказалось, при отладке кода, загруженного таким способом нельзя использовать в нём точки остановки!  Точнее, расставить можно, но они не работали. Опять облом! Или нет? Как выяснилось потом– нет! Есть способ и для такой загрузки! Но я на тот момент об этом ещё не знал, и посему пришлось искать третий способ загрузки (фэйспалм).

Итак, третий способ загрузить и исполнить код из файла, вообще оказывается «на поверхности плавал»! Но, как зачастую и бывает у новичков в открываемом для себя языке программировании, я к нему пришел в последнюю очередь. В Lua есть хороший метод  расширения базового функционала, называемый модули. Осуществляется он путем объявления перед использованием кода модуля  запроса на его загрузку через оператор require.  Опять же, для справки, под модулями в Lua подразумевается довольно большой набор разных вариантов внешнего кода, но в том числе и это коды на самом языке Lua. В каждом конкретный случае  способ загрузки скрыт за вызовом этого самого оператора  require. После недолгих экспериментов, опытным путём, я пришел к тому, что при загрузке модуля на Lua, указанный по имени модуль ищется как файл init.lua в папке с названием модуля. Причем, что интересно, такой способ поиска в среде Lua «МойОфис» искал и по абсолютному пути к рабочей папке текущей надстройки! Проще говоря, достаточно в корне папки надстройки создать подпапку, скажем с именем /forms   и поместить в неё файл init.lua, чтобы по запросу require («forms») загружалось содержимое из файла init.lua. Что мне собственно говоря и требовалось!

Содержимое файла entryform.lua для загрузки через require

frm=require ("forms")
Actions = {}
function Actions.getCommands()
	return {
			{
				id = "DlgForm.ShowDlg",
				menuItem = "Показать форму",
				command = Actions.ShowControls
			}
	}   
end
function Actions.ShowControls(context)	  
  if frm~=nil then --Проверка что вызываемый файл существует       
      context.showDialog(frm.dlgBegin) --Запускаем первую форму, а остальная логика будет в форме     
  else
    EditorAPI.messageBox("No Exists module 'forms'!") --Вывод сообщения что файла с формами не существует в требуемом каталоге.
  end  
end
Структура проекта надстройки по третьему способы загрузки через require

Структура проекта надстройки по третьему способы загрузки через require

В таком способе все, в общем-то, прекрасно. Можно наштамповать сколько угодно таких «внутренних модулей». Их легко и наглядно загружать. Можно модулям давать понятные названия в соответствии с функционалом и в папках модуля дополнительно хранить какие — то дополнительные ресурсы, относящиеся к нуждам именно этого модуля.  К ним легко обращаться. Их можно без проблем использовать множество раз, просто копируя в разные надстройки. Точки остановки при удалённой отладке при таком способе работают сразу «из коробки». Единственный облом — способ просмотра изменений не динамический, как в случае с загрузкой через loadstring ().

Каким способом пользоваться? Пока точного совета дать не могу. Плюсы и минусы которые я увидел на текущий момент, я описал.

Подключаем отладку

Теоретически, можно конечно и без неё (видимо этим и руководствуются разработчик). Если есть какая-то ошибка в коде, то при попытке загрузить такую надстройку выводится такое вот сообщение:

Встроенная диагностика при неудачной загрузке надстройки в

Встроенная диагностика при неудачной загрузке надстройки в «МойОфис»

Если начать на кнопку »Показать подробности» можно конечно найти где ошибся. Но выводится по одной ошибке за одну попытку. Да и диагностическое сообщение не сказать что информативное:

Расшифровываю: в глобальной переменной context, не дописана буква t

Расшифровываю: в глобальной переменной context, не дописана буква t

К счастью, оказывается, что для удалённой отладки, в Lua  имеется достаточно много уже готовых модулей. И к ещё большей удаче, оказалось что разработчики «МойОфис» для своей диагностики используют модуль »mobdebug»! И именно такой же, использует в своём отладчике по умолчанию и LuaRT! Правда, версии немного разнились, но и из «коробки» всё заработало как надо. Но на всякий случай, я перекинул более новую версию файла из LuaRT в «МойОфис» (в папку по пути C:\Program Files\MyOffice\Lua532\modules). Далее, путем достаточно длительных экспериментов, и чтения разных советов в интернете (куда же без этого?!), в файле entryform.lua надо добавить вот примерно такой код:

dbg=require ("mobdebug")
if dbg~=nil then
  dbg.start()
end

Кстати, добавить можно не обязательно именно в этом файле. Иногда удобнее запускать модуль отладки из вызываемых файлов.

Так же, надо сделать следующие изменения в файле настроек для самого LuaRT, без которых в дальнейшем отладка может пойти неверно, или вообще не будет работать в вызываемых дополнительных файлах (для второго способа через loadstring ()):

Вызов файла локальных настроек пользователя в IDE LuaRT

Вызов файла локальных настроек пользователя в IDE LuaRT

Вносимые изменения, требуемые для отладки

Вносимые изменения, требуемые для отладки

1.       debugger.hostname=«localhost» В целом можно и не делать, так как отладка и так будет на одном и том же компьютере

2.       debugger.verbose=true Данный пункт указывает делать подробный разбор отладочной информации в окне »Output». Так же помогает более точно отрабатывать отладку по способу 2 загрузки внешних файлов со скриптами

3.       editor.autoactivate = true Так же требуется для корректной работы отладки для способа загрузки 2

4.       debugger.runonstart = true Должен по идее запускать отладчик при запуске LuaRT, но у меня почему то этого не происходит, и старт сервера я делаю вручную.

Теперь сохраняем открытый файл user.lua и можно его закрыть. Сразу хочу описать ловкий момент, который появляется с добавлением модуля »mobdebug», и о котором я «случайно» прочел уже тогда, когда отработал отладку через загрузку своих модулей по require  (напомню, для такого способа точки остановки отрабатывают во всех файлах). А именно, как я уже упомянул выше, что меня подвигло искать способ загрузки номер 3, так это не работающие точки остановки в подгружаемые через loadstring () файлы! Это легко устраняется достаточно в loadstring () добавить второй параметр, обозначающий физический путь к модулю как к загружаемому файлу источнику! Причем, что было просто чудесно, путь можно указывать относительным! Правда, если файл был расположен не в этой же, а лежащей например рядом папке модуля (к примеру в /forms/init.lua) по отношению к вызывающему скрипту (допустим  из /cmd/entryform.lua), то в вызове надо указать возврат на уровень выше (первой ставится точка, потом слэш) :

local f, sError =loadstring(outContent,”./forms/init.lua”)

Ну вот, теперь можно начинать отладку. Точка остановки срабатывает только на функциях, поэтому  для проверки работоспособности отладчика добавим в форму обработчик (в файле /forms/init.lua или /cmd/forms.lua, в зависимости от типа загрузки) нажатия на кнопки Ок или Cancel:

Frm.dlgBegin:setOnDone(function(ret)
	if ret == 1  then		
		EditorAPI.messageBox("Click Ok!")
	else
		EditorAPI.messageBox("Click Cancel!")
	end
end
) 

Не забываем сохранить внесённые изменения! Теперь пробуем тестировать отладку. К сожалению, удалённая отладка под »mobdebug»  не является стабильной, и ряд действий при этом скорее сродни шаманству, иначе может привести к срыву отладки, и даже к аварийному закрытию программы пакета «МойОфис», но это лучше чем ничего.

При первой загрузке LuaRT придется вручную запустить сервер отладки:

Кнопка запуска сервера отладки и заодно и продолжения отладки после остановки

Кнопка запуска сервера отладки и заодно и продолжения отладки после остановки

Не смотря на то, что в окне »Output» скорее всего будет сообщение об ошибке о не найденных переменных или модулях (это нормально, так как пока проект скрипта запущен как локальный в LuaRT, а он знать не знает ничего про функционал надстроек!), сам сервер тем не менее запущен. Теперь надо «правильно» расставить точки останова. Я рекомендую поставить обязательно(!) одну в стартовом модуле, на функцию вызова из контекста формы:

Точка отладки в модуле

Точка отладки в модуле

На самом деле, эта точка нам не интересна, но почему то, без неё оталчик по вложенным в другие файлы точки остановки скорее всего не отработает!

Поставим для проверки точки остановки в файле /forms/init.lua (или для второго способа загрузки в /cmd/forms.lua) , на анонимной функции, отрабатывающей нажатия на стандартные кнопки Ok или Cancel:

74ae95c0b3adb3b9e2ba7fb01a86ff1d.jpg

Теперь запускаем приложение текстового процессора »МойОфис Текст» и по окончании его загрузки, в окне »Output», должно появится примерно такое вот сообщение:

Предпоследняя строчка говорит о том, что наша надстройка из подключилась из

Предпоследняя строчка говорит о том, что наша надстройка из подключилась из «МойОфис Текст» к серверу отладки LuaRT

При запуске надстройки из под программы (как запускать было в прошлой статье), выполнение надстройки должно будет остановиться вот в этом месте:

Сработала первая точка остановки (до запуска формы)

Сработала первая точка остановки (до запуска формы)

Как видно, можно посмотреть стэк вызовов, и даже значения глобальных переменных (если навести на них мышку). Более подробная информация о переменной при этом появляется автоматически внизу в окне »Output».
А вот дальше  есть особенности, которые были поняты опытным путём. Если мы хотим получить доступ к точкам остановки расставленным внутри /forms/init.lua (или для второго способа загрузки в /cmd/forms.lua), то дальше лучше пойти пошагово через команду »Step Over (Shift+F10)» :

e8af4870ebec3083d8a293a067684f2d.jpg

Иначе может получиться ситуация при которой, почему-то (и не всегда!), дальнейшие точки остановки в вызываемых скриптах не срабатывают! Ещё раз нажимаем на кнопку Step Over (Shift+F10) и окне приложения «Мой офис» должна открыться форма надстройки, которую мы отлаживаем.  В точку отладки внутри файла форм /forms/init.lua (или для второго способа в /cmd/forms.lua) попадаем при нажатии на форме надстройки на кнопку »Ок» или »Cancel»:

4ac311dd3549494b03eb6c979bb4e154.jpg

Так же становится доступным окно »Variables», в котором появляются значения разных переменных доступных внутри отлаживаемого модуля в текущий момент. Продолжение отладки выполняется той же кнопкой, что и запуск сервера отладки (F5).

Вот вкратце почти всё про отладку на текущий момент. Иногда конечно происходят сбои, и некоторые могут даже закрыть родительское приложение. Тогда надо просто перезапустить все приложение. Но, чаще всего, никакого такого фатала нет, и можно вполне нормально проводить отладку. В любом случае, так лучше чем вообще никак! В самом модуле отладке»mobdebug» есть много функций для облегчения отладки (распечатка стека вызова процедур, работа с корутинами, и много чего полезного), но поскольку пока речь по другой теме, на таком уровне отладки пока и остановлюсь! Обсудим более глубокие моменты отладки, по мере необходимости.

На этом сегодня всё! В следующей статье начнём «творить разное» с формами и добавим автозаполнение в LuaRT, хотя для объектов API Lua »МойОфис» относящихся к созданию форм (Forms: и ui: ), чтобы не шариться по справочникам при программировании. Спасибо всем, кто дошел до этого места!

© Habrahabr.ru