- PVSM.RU - https://www.pvsm.ru -

О разработке одного desktop-приложения на Python

Всем привет. В этой статье я хочу рассказать о разработке программы с открытым исходным кодом для оффлайнового хранения заметок — OutWiker. Этим проектом я занимаюсь в свободное время, первая версия программы вышла в далеком 2010 году, и OutWiker до сих пор продолжает развиваться. Так уж исторически сложилось, что кодом я занимаюсь практически в одиночку (хотя изредка получаю полезные отдельные патчи), но зато пользователи активно участвуют в переводе программы на свой родной язык и иногда присылают стили оформления для страниц, которые затем я включаю в сборку. А уж о том, сколько интересных идей они присылают, и говорить не стоит.

Что такое OutWiker

Когда-то я писал об этой программе на Хабре, но это было так давно, что прежде чем говорить об особенностях внутреннего устройства и процесса разработки, нужно сказать, что представляет собой программа с точки зрения пользователя. Все ссылки, связанные с проектом даны в конце статьи. Итак, OutWiker — это программа для хранения заметок в виде дерева, в англоязычном интернете такой софт обычно называют outliner (поэтому у программы такое название). Среди более известных «коллег» OutWiker с подобным функционалом можно назвать Zim, WikidPad, CherryTree, и множество других (ну и, разумеется, org mode для Emacs). Логичный вопрос с точки зрения пользователя — чем OutWiker отличается от других представителей древовидных записных книжек. На данный момент, по прошествии такого количества времени с момента начала разработки, я уже не готов развернуто сравнить весь этот софт. В стародавние времена я перепробовал десятки outliner-ов, штук пять использовал достаточно долгое время, но везде чего-то не хватало, хотелось одну возможность взять из WikidPad, другую — из викидвижка, который может работать оффлайново и т.д. Поэтому в качестве ответа на такой вопрос просто перечислю основные особенности, которыми обладает OutWiker.

О разработке одного desktop-приложения на Python - 1

  1. Все заметки хранятся в виде папок на диске. Это сделано по двум причинам — для надежности, чтобы, например, при постепенном умирании харда все заметки не отправились на тот свет вместе с одним файлом. И, кроме того, это позволяет просматривать и редактировать заметки без OutWiker. Разумеется, в таком способе хранения есть свои недостатки, но ничего не дается просто так.
  2. К каждой заметке можно прикреплять произвольное количество файлов (на самом деле и папок, но эта возможность не особо афишируется). Одно из назначений прикрепленных файлов (в основном картинок) — это использование их в тексте заметок. Таким образом, изменяя прикрепленную картинку, мы сразу видим новую картинку в тексте страницы.
  3. У OutWiker нет визуального редактора (я думаю, что он когда-нибудь появится, но пока руки до него не доходят), но есть несколько типов страниц — HTML-страницы, викистраницы и Markdown-страницы (после установки соответствующего плагина). В OutWiker упор сделан на викистраницы. Викинотация напоминает pmWiki (не очень распространенный движок для сайтов), но с некоторыми отличиями, об особенностях викинотации и ее реализации я скажу ниже.
  4. Как вы уже поняли по предыдущему пункту, программа умеет работать с плагинами, которых на данный момент больше 20. Например, с помощью плагинов для викистраниц можно добавить раскраску кода для разных языков программирования, поддержку формул в формате LaTeX, возможность вставки разных счетчиков (например, номеров рисунков), графики и диаграммы по данным, можно добавить шаблоны для часто вводимых фраз, создавать страницы, скачивая их из интернета и многое другое.
  5. Каждая заметка может быть помечена произвольным количеством тегов. Облако тегов показывается сбоку в окне программы.
  6. Программа кроссплатформенная, есть сборки под Windows и Linux (с Mac OS X у меня пока дела не складываются).
  7. Исходный код открыт и распространяется по лицензии GPL 3.

О разработке одного desktop-приложения на Python - 2

О разработке одного desktop-приложения на Python - 3

Кое-что о внутренней кухне разработки

Язык и основные библиотеки

Теперь я расскажу, как OutWiker устроен изнутри, а также с какими проблемами пришлось столкнуться при разработке.

Весь проект написан на Python, причем на Python 2.7, переход на Python 3.x планируется, но не в ближайшее время (почему, скажу чуть позже). Для создания интерфейса используется библиотека wxPython. На этапе зарождения проекта в качестве библиотеки для интерфейса выбирал между wxPython и PyQt, остальные подобные библиотеки были отвергнуты, потому что они создают интерфейс, который выглядит чужеродно или под Windows, или под Linux, и может быть и там, и там. В результате остановился на wxPython из-за того, что размер программы получался меньше. Точные цифры размеров я уже не назову, да и проект с того времени уже сильно разросся. Выбором wxPython я доволен, хотя с ней были связаны некоторые неприятные моменты (о них тоже скажу позже).

Язык Python выбрал, потому что у него все замечательно с кроссплатформенностью. Изначально планировалось добавить в программу возможность создания плагинов, а поскольку плагины — это обычные Python-скрипты, то нет надобности их отдельно собирать под разные операционные системы. В общем, о выборе языка я нисколько не жалею, и тормозов, которые бы возникали именно из-за языка, замечено не было. Есть участки программы, которые работают не очень быстро, но там причины в другом, в основном в необходимости чтения большого количества файлов с диска.

Что касается кроссплатформенности, то в программе не так много мест, работа которых зависит от операционной системы, и все они связаны с интерфейсом. Пожалуй, наибольшее различие заключается в способе отображения HTML-страниц. Под Windows используется движок Internet Explorer, а под Linux — WebKit. Раньше OutWiker работал под wxPython 2.8 и движки Internet Explorer и WebKit использовались с помощью хаков, которые перестали быть актуальными с выходом wxPython 3.0, теперь классы для работы с этими движками встроены в библиотеку. Правда, Internet Explorer я продолжаю использовать через хак с вызовом COM-объекта, потому что такое использование дает больше возможностей для настроек поведения движка. Теоретически у класса wx.html2.WebView, который должен под Windows работать через Internet Explorer, а под Linux через WebKit, есть метод GetNativeBackend(), который должен вернуть указатель на «настоящий» движок, но почему-то у меня этот метод всегда возвращал None.

Надо признаться, что на проект большое влияние оказала программа WikidPad. Я сам ей долго пользовался, она тоже написана на Python + wxPython, и иногда я даже подсматривал в ее исходники, чтобы посмотреть, как некоторые моменты там сделаны. Надо сказать, что такое подсматривание сэкономило немало времени.

Викинотация

Для страниц с оформлением предназначены три типа страниц: HTML-страницы, Markdown-страницы (требуется добавить соответствующий плагин) и викистраницы. То, что в программе должны быть викистраницы я решил с момента зарождения проекта. В отличие от HTML, викинотации более лаконичны и их легко расширять, добавляя новые команды. Другое преимущество викинотаций — это возможность преобразования встроенного текстового описания в некий графический объект. Например, в OutWiker есть команда для вставки формул в формате LaTeX, вставки графиков, которые строятся либо по данным из текстового файла, либо по данным, которые вставлены непосредственно в код страницы, есть плагин, который из текстового описания создает описание диаграмм и т.п. Кроме того, поскольку викистраница в итоге преобразуется в HTML для отображения пользователю, то это тоже можно использовать, например, при подготовке статей на сайт, если движок сайта ожидает ввода в формате HTML. Например, сейчас эту статью я пишу в виде викинотации, а в конце переключусь на вкладку HTML и получу готовый HTML-код, который нужно будет только немного подогнать под особенности конкретного сайта.

Викинотация:

О разработке одного desktop-приложения на Python - 4

Результат:

О разработке одного desktop-приложения на Python - 5

HTML:

О разработке одного desktop-приложения на Python - 6

В качестве викинотации я ориентируюсь на движок pmWiki [1], он не очень распространен, но у меня уже был опыт работы с ним и мне нравится продуманность его нотации. В некоторых моментах при реализации парсера я отходил от оригинальной нотации, но в целом стараюсь придерживаться ее. В эту викинотацию очень легко добавлять свои команды в формате (:command_name:)...(:command_nameend:).

Иногда спрашивают, почему я не использовал изначально Markdown, но на мой взгляд это слишком ограниченный язык, к тому же pmWiki более логичный. Например, в Markdown я вечно путаюсь с порядком аргументов для ссылки: [текст](http://example.com/) — два вида скобок, еще важен их порядок. В используемой викинотации ссылка оформляется в виде: [[текст -> example.com [2]]]. Стрелка указывает направление ссылки. На самом деле есть еще второй формат ссылок, но лично я обычно пользуюсь таким.

Кстати, в OutWiker не обязательно запоминать викинотацию, все элементы оформления можно вставлять через меню или с помощью панели инструментов. Для HTML- и Markdown-страниц такие кнопки и пункты меню тоже имеются.

Для создания википарсера используется очень удобная библиотека pyparsing [3]. Удобная она в первую очередь тем, что представляет собой всего лишь один файл pyparsing.py, который можно положить в исходники и не тянуть лишнюю зависимость в проекте. С помощью этой библиотеки описываются все используемые токены викинотации, а затем они преобразуются в HTML. Про pyparsing есть подробная документация с множеством примеров и даже небольшая книжка. Кроме того, автор библиотеки активно участвует на форуме на сайте и готов помочь с использованием библиотеки.

Локализация

Над переводом OutWiker на другие языки активно работают некоторые пользователи, за что им огромное спасибо. Для совместного перевода используется сервис crowdin.com [4]. Это достаточно удобный сервис, на котором пользователи могут предлагать свои варианты перевода фраз, обсуждать их.

О разработке одного desktop-приложения на Python - 7

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

Вот, например, как выглядит украинская и шведская локализация.

О разработке одного desktop-приложения на Python - 8

О разработке одного desktop-приложения на Python - 9

Для использования локализаций внутри OutWiker используется стандартный модуль gettext.

Особенности сборки

Поскольку Python — интерпретируемый язык программирования, то для того, чтобы пользователь мог запустить программу, теоретически у него должны быть установлены Python и все необходимые библиотеки. Разумеется, заставлять обычных пользователей устанавливать Python, wxPython и другие требуемые библиотеки, да еще и нужных версий, негуманно. Пользователь должен скачать программу, распаковать ее установить с помощью инсталятора и метода «Next — Next — Next», после чего запустить иконку на рабочем столе. К счастью, для программ, написанных на Python, это все делается сравнительно просто. Если говорить про Windows, то интерпретатор Python без стандартных библиотек умещается в dll-ку размером чуть больше 3 МБ. Существуют несколько утилит, которые делают запускаемые файлы (exe-шники, если говорить про Windows) из Python-скриптов. Наиболее известные из них — cx_Freeze [5] и pyInstaller [6]. По возможностям они примерно равноценны (хотя pyInstaller позволяет создавать единственный exe-шник из Python-скрипта, а у cx_Freeze такой возможности нет), но у них разных подход к тому, где хранить файлы *.pyc библиотек, и по поводу того, как задавать параметры сборки.

Для создания exe-шников OutWiker долгое время использовался cx_Freeze. Проблем с ним не было, пока не вышел cx_Freeze 5.0, в этой версии автор сильно переписал внутренности утилиты. Видно из-за этого что-то пошло не так. Сборка OutWiker, созданная с помощью cx_Freeze 5.0 начала виснуть при старте, причем судя по результатам отладки, на ровном месте, где никаких проблем не ожидается (скорее всего это как-то было связано с многопоточностью). После выхода cx_Freeze 5.0.2 эта проблема исчезла, но OutWiker перестал выгружаться из памяти при закрытии программы. Эту проблему можно было решить явным вызовом sys.exit(0) в конце программы, но это костыль, который не хотелось добавлять. К тому же, кто знает, какие еще проблемы могли возникнуть при использовании cx_Freeze 5.0.x. Можно было бы остаться на проверенной временем cx_Freeze 4.x, но я не люблю использовать устаревшие библиотеки. Тогда было решено перейти на pyInstaller. Переезд оказался достаточно быстрым, буквально за один день. Никаких проблем с новой сборкой не возникло. Поэтому теперь для сборки используется pyInstaller. С точки зрения пользователей внешне ничего не должно поменяться.

Те же самые cx_Freeze / pyInstaller можно использовать и для создания бинарных сборок под Linux, чтобы полученная сборка могла запускаться в разных дистрибутивах Linux. Под Windows из полученной сборки создается инсталятор с помощью Inno Setup, а под Linux из подобной сборки создается deb-пакеты (на виртуальных машинах, что будет описано ниже).

Другая особенность сборки заключается в том, как запускаются разные цели сборки. Все начиналось с небольшого Makefile, который постепенно рос, и проблем с ним становилось все больше. В основном они были связаны с кроссплатформенностью — поддерживать единый Makefile под Windows и Linux было тяжело. В свое время мне это надоело и я начал искать альтернативу, желательно, чтобы она была написана на Python. Альтернатива нашлась достаточно быстро в виде программы Fabric. Теперь все бывшие цели Makefile переписаны в обычные функции Python, да и вообще система сборки разрослась неимоверно. Вот, например, как сейчас выглядит список задач Fabric.

$ fab -l
Available commands:

    apiversion            Print current OutWiker API versions
    apiversions           Print current OutWiker API versions
    build                 Create artefacts for current version.
    clear                 Remove artefacts after all assemblies
    create_tree           Create wiki tree for the tests
    deb                   Assemble the deb packages
    deb_binary            Create binary deb package
    deb_binary_clear      Remove binary deb package
    deb_clear             Remove the deb packages
    deb_install           Assemble deb package for current Ubuntu release
    deb_single            Assemble the deb package for the current Ubuntu release
    deb_sources_included  Create files for uploading in PPA (including sources)
    deploy                Upload unstable version to site
    doc                   Build documentation
    linux_binary          Assemble binary builds for Linux
    linux_clear           Remove binary builds for Linux
    locale                Update the localization file (outwiker.pot)
    locale_plugin         Create or update the localization file for pluginname plug-in
    outwiker_changelog    Generate OutWiker's changelog for the site
    plugin_changelog      Generate plugin's changelog for the site
    plugin_locale         Create or update the localization file for pluginname plug-in
    plugins               Create an archive with plugins (7z required)
    plugins_clear         Remove an archive with plugins (7z required)
    plugins_list          Print plugins list for th site
    prepare_virtual       Prepare virtual machine
    run                   Run OutWiker from sources
    site_versions         Compare current OutWiker and plugins versions with versions on the site
    sources               Create the sources archives
    sources_clear         Remove the sources archives.
    test                  Run the unit tests
    test_build            Run the build unit tests
    upload_binary         Upload unstable version to site
    upload_plugin         Upload plugin to site
    upload_plugins_pack   Upload archive with all plugins to site
    vm_halt               Stop virtual machines for build
    vm_linux_binary       Create 32- and 64-bit assembly on virtual machines
    vm_prepare            Prepare virtual machines for build
    vm_remove_keys        Remove local SSH keys for remote virual machines
    vm_run                Run virtual machines for build
    vm_stop               Stop virtual machines for build
    vm_update             Update the virtual machines
    win                   Build OutWiker for Windows with cx_Freeze
    win_clear             Remove assemblies under Windows

Как видите, команд достаточно много, все они описаны в документации [7]. Сюда входят команды для сборки под Windows и Linux, запуска тестов, работы с виртуальными машинами для сборки, обновления локализаций, выкладывания новых версий на сайт и другие. Несмотря на то, что, судя по документации, Fabric больше ориентирован на работу с удаленными серверами (условный аналог Ansible), но в качестве замены Makefile мне он очень нравится.

Войны с Ubuntu

С wxPython были связаны еще несколько неприятных моментов, которые, к счастью, удалось достаточно быстро решить. Все эти проблемы возникали под Ubuntu, на которую я в первую очередь ориентируюсь при разработке (из-за того, что у меня это основная операционная система). Первый неприятный момент был связан с переходом с wxPython 2.8 на 3.0 (не путать с Python 3.x). Дело в том, что при переходе с версии 2.8 к 3.0 были некоторые проблемы с обратной совместимостью, но, к счастью, эти две версии можно было ставить одновременно и из python-скрипта выбирать нужную версию с помощью wxversion. (с выходом wxPython 4.0 такой возможности больше не будет). Поскольку Python — интерпретируемый язык, и в Ubuntu он всегда присутствует, то для запуска под этой операционной системой нет необходимости создания бинарных сборок, а просто при установке deb-пакета исходники раскидываются по нужным папкам и запускаются с помощью команды python runoutwiker.py. Все необходимые библиотеки для работы wxPython и WebKit указаны как зависимости. Однако с выходом Ubuntu 16.04 LTS возникли две проблемы. Первая была связана с тем, что в этой версии Ubuntu убрали wxPython 2.8, поэтому пришлось срочно мигрировать на wxPython 3.0 и отказаться от поддержки другой долгоживущей версии Ubuntu 14.04 LTS. Но это еще не все, оказалось, что в Ubuntu 16.04 (в следующих версиях это исправили) wxPython как-то неправильно скомпилирован, и в нем без шаманств не работает движок WebKit. К счастью, тут помогли продвинутые пользователи OutWiker, которые подсказали, что проблема обходится с помощью LD_PRELOAD и указанием пути до одной библиотеки, относящейся к wxPython. Как я уже сказал, надобность в этом костыле в последующих версиях Ubuntu отпала, но ради поддержки Ubuntu 16.04 его приходится использовать до сих пор.

Чтобы в будущем обезопасить себя и пользователей от подобных изменений в Ubuntu, недавно начал делать бинарные сборки под Linux. Такие сборки делаются для 32- и 64-битных систем. В перспективе это позволит самостоятельно компилировать wxPython с нужными параметрами и не зависеть от имеющихся библиотек в репозиториях Ubuntu. Для того, чтобы создавать бинарные сборки (и deb-пакеты на основе их) используется связка Ansible, Vagrant и VirtualBox. С помощью Vagrant запускаются две виртуальные машины (32 и 64 бита), туда с помощью Ansible через SSH закачиваются исходники программы, там создаются бинарные сборки и deb-пакеты, которые скачиваются обратно на рабочий комп (хост). Но недавно и тут разработчики Ubuntu подложили свинью. Дело в том, что виртуальные машины создаются на основе Ubuntu 17.04, и в перспективе я собирался переводить виртуальные машины на Ubuntu 17.10. Я бы рад был использовать для сборки версию Ubuntu 16.04 LTS, но, как я уже говорил, там есть проблемы с wxPython. Но внезапно разработчики Ubuntu решили отказаться от поддержки 32-битных систем. Возможно, в будущем для сборки на виртуальных машинах придется переходить на другой дистрибутив. Конечно, можно было бы тоже отказаться от поддержки 32-битных систем, но в данный момент такая поддержка мне как разработчику не мешает, а пользователям может быть полезна.

Другие используемые библиотеки

Кроме уже упоминавшихся библиотек wxPython и pyparsing в проекте используются и другие библиотеки. Коротко перечислю их.

  • PyEnchant [8]. Используется для проверки орфографии.
  • Pygments [9]. Используется в плагине Source для раскраски исходников кусков кода. На вход библиотеке мы подаем исходник, на выходе получаем красиво оформленный HTML. Pygments понимает огромное количество языков программирования, в том числе экзотических и редко используемых, а также дает выбирать стиль оформления (к библиотеке прилагается более десятка стилей).
  • Python Markdown [10]. Как следует из названия, предназначен для разбора нотации Markdown.
  • KaTeX [11]. Используется в плагине TeXEquation. JavaScript-библиотека для рендеринга формул в формате LaTeX. Раньше использовалась программа mimeTeX, но формулы, созданные с помощью нее выглядели не очень эстетично и были проблемы с отображением формул на страницах с темным фоном, потому что mimeTeX создает картинку с черным текстом.
  • Beautiful Soup [12]. Используется в плагине WebPage, предназначенном для создания страниц из страниц в интернете (при этом скачиваются все картинки, стили CSS и скрипты JavaScript). Библиотека используется для разбора и правки скачанного HTML-кода. Например, чтобы заменить исходные ссылки на картинки в HTML-коде на пути до скачанных картинок.
  • Chardet [13]. Используется в том же плагине WebPage, чтобы определить кодировку скачанной страницы.
  • Jinja2 [14]. Используется в плагине Snippets, который предназначен для вставки в заметки шаблонного текста. Шаблоны создаются в формате Jinja (с некоторыми упрощениями).
  • Blockdiag [15]. Используется в плагине Diagrammer, который по текстовому описанию строит схему из кубиков, связанных между собой стрелками. Основную работу в плагине выполняет библиотека Diagrammer. Ее можно рассматривать как упрощенную версию dot [16] и Graphviz [17].

Планы на дальнейшее развитие

Проект OutWiker активно развивается, доказательство чему статистика коммитов на github:

О разработке одного desktop-приложения на Python - 10

Сейчас исходники занимают около 90 МБ, куда входят почти 4300 файлов, из них 1772 — python-скрипты.

Планов и идей огромное количество (количество issues на github сейчас чуть меньше 350). Туда попадают любые пожелания пользователей, даже если в ближайшее время до их реализации руки точно не дойдут. Поскольку кодом занимаюсь я практически в одиночку, то новые возможности появляются не так быстро, как хотелось бы. В последнее время стабильные версии OutWiker выходят примерно раз в год, но каждый месяц я выкладываю нестабильные версии. На самом деле их нестабильность — вопрос философский, потому что я не выкладываю откровенно неработающие версии, и иногда нестабильные версии «стабильнее» стабильных в том плане, что после релиза в них исправляются какие-то найденные не критичные недоработки из последнего релиза. Но в нестабильных версиях я могу себе позволить что-то не успеть отшлифовать, а может быть что-то не заметить после добавления новой возможности.

В данный момент работа идет по двум направлениям — сделать более современный внешний вид программы и уменьшить количество файлов, создаваемых для каждой заметки. Что касается интерфейса, то я хочу избавиться от имеющихся панелей инструментов и сделать их стиле Delphi. Но скорее всего эту глобальную переделку оставлю до следующей версии 2.2.

В свете описанных выше вечно возникающих проблем с Ubuntu хочется попробовать под Linux сделать snap-пакет или/и flatpak-пакет.

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

В более дальней перспективе нужно будет перейти на wxPython 4.0, который, надеюсь, скоро получит статус релиза, и на Python 3.x. Логично сначала будет перейти на wxPython 4.0, который поддерживает обе версии Python, а потом перейти на Python 3.

Ссылки

Автор: Jenyay

Источник [22]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/razrabotka/267148

Ссылки в тексте:

[1] pmWiki: http://www.pmwiki.org/

[2] example.com: http://example.com/

[3] pyparsing: http://pyparsing.wikispaces.com/

[4] crowdin.com: https://crowdin.com/project/outwiker

[5] cx_Freeze: https://anthony-tuininga.github.io/cx_Freeze/

[6] pyInstaller: http://www.pyinstaller.org/

[7] в документации: http://outwiker.readthedocs.io/ru/latest/ru/build/fabfile.html

[8] PyEnchant: http://pythonhosted.org/pyenchant/

[9] Pygments: http://pygments.org/

[10] Python Markdown: https://pypi.python.org/pypi/Markdown

[11] KaTeX: https://github.com/Khan/KaTeX

[12] Beautiful Soup: https://www.crummy.com/software/BeautifulSoup/bs4/doc/

[13] Chardet: https://pypi.python.org/pypi/chardet

[14] Jinja2: http://jinja.pocoo.org/docs/dev/

[15] Blockdiag: http://blockdiag.com/en/

[16] dot: https://ru.wikipedia.org/wiki/DOT_(%D1%8F%D0%B7%D1%8B%D0%BA)

[17] Graphviz: https://ru.wikipedia.org/wiki/Graphviz

[18] Страница программы: http://jenyay.net/Soft/Outwiker

[19] Страница нестабильных версий: http://jenyay.net/Outwiker/Unstable

[20] Проект на Github: https://github.com/Jenyay/outwiker

[21] Техническая документация: http://outwiker.readthedocs.io/ru/latest/

[22] Источник: https://habrahabr.ru/post/341404/