- PVSM.RU - https://www.pvsm.ru -
Всем привет. В этой статье я хочу рассказать о разработке программы с открытым исходным кодом для оффлайнового хранения заметок — OutWiker. Этим проектом я занимаюсь в свободное время, первая версия программы вышла в далеком 2010 году, и OutWiker до сих пор продолжает развиваться. Так уж исторически сложилось, что кодом я занимаюсь практически в одиночку (хотя изредка получаю полезные отдельные патчи), но зато пользователи активно участвуют в переводе программы на свой родной язык и иногда присылают стили оформления для страниц, которые затем я включаю в сборку. А уж о том, сколько интересных идей они присылают, и говорить не стоит.
Когда-то я писал об этой программе на Хабре, но это было так давно, что прежде чем говорить об особенностях внутреннего устройства и процесса разработки, нужно сказать, что представляет собой программа с точки зрения пользователя. Все ссылки, связанные с проектом даны в конце статьи. Итак, OutWiker — это программа для хранения заметок в виде дерева, в англоязычном интернете такой софт обычно называют outliner (поэтому у программы такое название). Среди более известных «коллег» OutWiker с подобным функционалом можно назвать Zim, WikidPad, CherryTree, и множество других (ну и, разумеется, org mode для Emacs). Логичный вопрос с точки зрения пользователя — чем OutWiker отличается от других представителей древовидных записных книжек. На данный момент, по прошествии такого количества времени с момента начала разработки, я уже не готов развернуто сравнить весь этот софт. В стародавние времена я перепробовал десятки outliner-ов, штук пять использовал достаточно долгое время, но везде чего-то не хватало, хотелось одну возможность взять из WikidPad, другую — из викидвижка, который может работать оффлайново и т.д. Поэтому в качестве ответа на такой вопрос просто перечислю основные особенности, которыми обладает OutWiker.
Теперь я расскажу, как 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-код, который нужно будет только немного подогнать под особенности конкретного сайта.
Викинотация:
Результат:
HTML:
В качестве викинотации я ориентируюсь на движок 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]. Это достаточно удобный сервис, на котором пользователи могут предлагать свои варианты перевода фраз, обсуждать их.
На данный момент программа поддерживает английский, русский, украинский и шведский языки. К сожалению, поддержка некоторых языков прекратилась. Так, например, нужны переводчики, которые доделают итальянскую локализацию (итальянский парень, который когда-то активно поддерживал эту локализацию, куда-то пропал) и немецкую локализацию (сейчас часть немецкого перевода сделана немцем, а часть моей женой в качестве практики немецкого языка, который она учит. Надо, чтобы кто-то проверил и одобрил вторую часть перевода. Кроме того, на немецкий язык не переводились плагины). Есть несколько языков, на которые кто-то начинал делать переводы, но они не доведены до ума (болгарский язык и португальский бразильский). Ну и разумеется, любые другие переводы приветствуются.
Вот, например, как выглядит украинская и шведская локализация.
Для использования локализаций внутри 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 мне он очень нравится.
С 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 в проекте используются и другие библиотеки. Коротко перечислю их.
Проект OutWiker активно развивается, доказательство чему статистика коммитов на github:
Сейчас исходники занимают около 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/
Нажмите здесь для печати.