Fork me on GitHub

Объявление на тему компьютерного зрения

Недавно со мной связался Иван, который ищет человека себе в команду и он полагает, что интересный ему человек может быть среди лисперов. Я предложил опубликовать объявление в моём блоге, что бы он попал в Russian Lambda Planet (а также в планету на lisper.ru) и таким образом попытаться зацепить целевую аудиторию. Ниже его текст.

##############################################################

Мы команда из двух человек, последние пять лет занимаемся компьютерным зрением и машинным обучением. Пришло время расширять коллектив, т.к. работы больше, чем можем сделать, тем более есть постоянные заказчики, которым сложно отказать по причине нехватки времени.

Мы начали применять сверточные нейронные сети ("компьютерное зрение 2.0") раньше, чем об этом появилась первая русскоязычная статья в интернете. За последние пару лет у нас были заказы такого плана: видеоконтроль нештатных ситуаций на конвейере, распознавание блюд китайской кухни по фото со смартфона, поиск ключевых точек на лице в реальном времени, есть заказчик, с которым мы работаем по теме process mining (анализ процессов по журналу событий), кластеризации и прогнозированию процессов и прочим связанными вещами, были работы по распознаванию речи, ну и много всякой мелочевки типа "классифицировать картинки."

В качестве дополнительного понта могу указать, что на upwork-аккаунте имеем 5 звезд и job success rate 95%.

Кого мы ищем. Очевидно, не питаем надежд найти состоявшегося специалиста по компьютерному зрению, т.к. такие люди без работы не сидят. Впрочем, если таковой человек найдется, будет отлично. Вообще достаточно устойчивого интереса к этой области, а научиться необходимому можно по ходу дела, тем более, что помимо собственно математики в таких проектах есть существенная часть чистого программирования.

Другой важный момент - умение писать хороший код. Не потому, что будет нужно проектировать сложные архитектурные решения, а просто потому что это хорошо и облегчает поддержку этого кода спустя годы. По этой причине размещаю это в сообществе функционального программирования.

Из плюсов: удаленная работа, постоянное разнообразие задач, свобода выбора решений задач, отсутствие какого-либо официоза в отношениях внутри команды.

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

Ожидаемый доход полностью зависит от успешности всей команды и личного вклада участника. В среднем получается 130-150 тысяч рублей на человека за полный рабочий месяц. Для Москвы вероятно не самая большая сумма, но для провинции местами очень даже ничего.

Для прототипирования и обучения прогнозирующих моделей мы используем питон, в основном из-за обилия специализированных библиотек. Заказчиков разворачивать у себя питонье окружение обычно не устраивает, поэтому готовое решение обычно переписываем на Си. Это доставляет кучу боли, поэтому мы сейчас обдумываем разные варианты, на какой платформе выкатывать готовые решения. Пока склоняемся к common lisp, который был бы идеальным вариантом, если бы имел нормальный биндинг к OpenCV.

По любым вопросам пишите на hohlovivan@gmail.com

##############################################################

Парсим ODS

Внезапно потребовалось из программы на CL прочитать ODS файл. Подходящего решения не нашёл, правда особо не искал, потому что это довольно просто сделать самому:

  1. (ql:quickload "zip")
  2. (ql:quickload "cl-libxml2")
  3. (ql:quickload "parse-number")
  4. (defpackage #:my.tools.ods
  5.   (:use #:cl #:iter)
  6.   (:export #:parse-ods-file))
  7. (in-package #:my.tools.ods)
  8. (defun ods-xpath-map ()
  9.   (list* (list "office" "urn:oasis:names:tc:opendocument:xmlns:office:1.0")
  10.          (list "table" "urn:oasis:names:tc:opendocument:xmlns:table:1.0")
  11.          xpath:*default-ns-map*))
  12. (defun parse-ods-content (content)
  13.   (xtree:with-parse-document (doc content)
  14.     (let ((xpath:*default-ns-map* (ods-xpath-map)))
  15.       (iter (for sheet in (xpath:find-list doc "//table:table"))
  16.             (collect (parse-sheet sheet))))))
  17. (defun parse-sheet (sheet)
  18.   (list* (xpath:find-string sheet "@table:name")
  19.          (iter (for row in (xpath:find-list sheet "table:table-row"))
  20.                (collect (parse-row row)))))
  21. (defun parse-row (row)
  22.   (iter (for cell in (xpath:find-list row "table:table-cell"))
  23.         (let ((repeat-number (xpath:find-string cell "@table:number-columns-repeated")))
  24.           (dotimes (i (if repeat-number (parse-integer repeat-number) 1))
  25.             (collect (parse-cell cell))))))
  26. (defun parse-cell (cell)
  27.   (let ((value (xtree:text-content cell))
  28.         (value-type (xpath:find-string cell "@office:value-type")))
  29.     (cond
  30.       ((string= value-type "float")
  31.        (parse-number:parse-number value))
  32.       (t
  33.        value))))
  34. (defun parse-ods-file (pathname)
  35.   (zip:with-zipfile (zipfile pathname)
  36.     (parse-ods-content
  37.      (zip:zipfile-entry-contents
  38.       (zip:get-zipfile-entry "content.xml" zipfile)))))

Мне этого пока хватает.

Асинхронный RESTAS

Уже некоторое время на github доступна асинхронная версия RESTAS . Изменения довольно большие, рассказывать сейчас подробности не буду, но кому интересно, тот уже может посмотреть. Есть две небольших демки:

Там есть целый ряд разных косметический изменений, но главное теперь из обработчика маршрута можно возвращать promise (см. blackbird).

Вместо restas.asd теперь restas.hunchentoot.asd и restas.wookie.asd. Все "асинхронности" доступны только при использовании restas.wookie.

Подстветка кода с помощью colorize

Для подсветки кода на Common Lisp я люблю использовать colorize, потому что она добавляет в разметку ссылки на Hyperspec. А вот структура разметки получается та ещё, я хочу другую, более современную. Но исходники colorize ужасны и копаться в них я не хочу. Поэтому я написал функцию, которую берёт оригинальную разметку, генерируемую colorize и превращает её в нечто более интересное (для меня):

  1. (defparameter *span-classes*
  2.   '("symbol" "special" "keyword" "comment" "string" "character"))
  3. (defun update-code-markup (markup)
  4.   (labels
  5.       ((bad-span-p (node)
  6.          (and (string-equal (xtree:local-name node) "span")
  7.               (not (member (xtree:attribute-value node "class") *span-classes*
  8.                            :test #'string-equal))))
  9.       ;;---------------------------------------
  10.       (comment-p (node)
  11.          (and (string-equal (xtree:local-name node) "span")
  12.               (string-equal (xtree:attribute-value node "class") "comment")))
  13.       ;;---------------------------------------
  14.       (br-p (node)
  15.          (string-equal (xtree:local-name node) "br"))
  16.       ;;---------------------------------------
  17.       (flatten-spans (node)
  18.          (iter (for el in (xtree:all-childs node))
  19.                (flatten-spans el))
  20.         ;;---------------------------------------
  21.         (when (comment-p node)
  22.            (setf (xtree:text-content node)
  23.                  (xtree:text-content node))
  24.            (xtree:insert-child-after (xtree:make-element "br") node))
  25.         ;;---------------------------------------
  26.         (when (bad-span-p node)
  27.            (iter (for el in (xtree:all-childs node))
  28.                  (xtree:insert-child-before (xtree:detach el) node))
  29.            (xtree:remove-child node))))
  30.    ;;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  31.    (html:with-parse-html (doc (format nil "<div>~A</div>" markup))
  32.       (let ((div (xtree:first-child (xtree:first-child (xtree:root doc)))))
  33.         (flatten-spans div)
  34.         (xtree:with-object (fragment (xtree:make-document-fragment doc))
  35.          ;;---------------------------------------
  36.          (let* ((pre (xtree:make-child-element fragment "div"))
  37.                  (ol (xtree:make-child-element pre "ol")))
  38.             (setf (xtree:attribute-value pre "class")
  39.                   "prettyprint linenums")
  40.             (setf (xtree:attribute-value ol "class")
  41.                   "linenums")
  42.            ;;---------------------------------------
  43.            (iter (for line in (split-sequence:split-sequence-if #'br-p (xtree:all-childs div)))
  44.                   (for i from 0)
  45.                   (let ((li (xtree:make-child-element ol "li")))
  46.                     (setf (xtree:attribute-value li "class")
  47.                           (format nil "L~s" i))
  48.                    ;;---------------------------------------
  49.                    (iter (for el in line)
  50.                           (xtree:append-child li (xtree:detach el))))))
  51.          ;;---------------------------------------
  52.          (html:serialize-html fragment :to-string))))))

Здесь можно видеть не только код, но и непосредственный результат.

Кстати, функция довольно большая. Читать "сплошное месиво кода" на CL бывает не очень приятно. В других языках код обычно "разряжают" с помощью пустых строк. Но в CL это эстетически смотрится как-то не очень. Так что я решил попробовать использовать линии в качестве разделителей. Пока мне кажется, что получается неплохо.

Изменения в mongo-cl-driver

После длительного перерыва снова взялся за mongo-cl-driver и основательно его переделал.

Адаптеры

Для начала выкинул iolib. При чём, не могу сказать о ней ничего плохого, но... Во-первых, до сих пор я использовал mongo-cl-driver только в блокирующем режиме, мои надежды на то, что iolib откроет двери в мир асинхронного программирования не оправдываются - просто никто ничего такого не пишет, никакого веб-сервера и т.п. на iolib до сих пор не появилось. А между тем, Andrew Lyon активно пилит платформу на базе libevent: cl-libevent2, cl-async, drakma-async и даже асинхронный веб-сервер wookie (это при том, что кое-какой веб-сервер есть в самой libevent). Кстати, libevent отлично работает на винде.

В итоге, под впечатлением от pika добавил в mongo-cl-driver поддержку адаптеров и сейчас есть два работающих адаптера: на базе usocket и cl-async. Вернуть адаптер для iolib несложно, но пока не понятно что с ним вообще делать то.

Futures

Долго не мог решить, как же совместить в одной библиотеке синхронный и асинхронный интерфейс. Идти по пути node.js, где каждая функция представлена в двух вариантах (например, fs.readFile и fs.readFileSync) мне не хотелось. Как сделано в pika (каждый адаптер предоставляет свой набор классов) мне тоже не нравится. В итоге остановился на использовании futures : никакого разделения API на синхронный и асинхронный нет, но при использовании асинхронного адаптера возвращается не результат, а future. Сейчас мне кажется, что это очень удачный вариант, за одним но... Я пока не придумал, как нормально организовать обработку ошибок в таком варианте. cl-async-future (по моей просьбе автор выделил её в отдельную библиотеку) предоставляет API и набор макросов. Эти макросы работают просто замечательно если ошибок нет. А вот когда есть ошибки, случается что-то мутное. Пока план в том, что бы взять API и реализовать на его основе аналог Step, но этого я ещё не сделал. Собственно, поэтому пользоваться cl-async адаптером пока не стоит, но можно с ним экспериментировать.

MongoClient

Другим направлением работы было приведение драйвера в соответствии с официальными рекомендациями. В первую очередь, добавлен класс mongo:mongo-client, при создании которого указывается параметры сервера (класс mongo:server-config) и уровень Write Concern (класс mongo:write-concern).

Адаптеры наследуют от mongo:mongo-client и поэтому для создания клиента следует использовать функцию mongo:create-mongo-client:

  1. (mongo:with-client (client (mongo:create-mongo-client :usocket
  2.                                                       :server config
  3.                                                       :write-concern w))
  4.   (print (mongo:$count (mongo:collection (make-instance 'mongo:database
  5.                                                         :client client
  6.                                                         :name "blog"))
  7.                                          "posts"))))

Для упрощения задания Write Concern предопределены следующие константы:

  • mongo:+write-concern-normal+

  • mongo:+write-concern-fsync+

  • mongo:+write-concern-replicas+

  • mongo:+write-concern-journal+

  • mongo:+write-concern-fast+

  • mongo:+write-concern-crazy+

Другие изменения

У функций

  • mongo:update-op

  • mongo:insert-op

  • mongo:delete-op

опциональный параметры превращены в keys для того, что бы добавить key-параметр :write-concern.

Функция mongo:collection-count переименована в mongo:$count. Плюс добавленная функция mongo:$distinct, очень удобная, например, все тэги в этом блоге с её помощью я получаю так:

  1. (mongo:$distinct posts "tags")

Добавил функцию mongo:eval-js:

  1. (mongo:with-client (client (mongo:create-mongo-client :usocket))
  2.   (mongo:eval-js (make-instance 'mongo:database :mongo-client client :name "test")
  3.                  "function (x, y) { return x * y; }"
  4.                  :args #(2 3)))
  5. 6.0d0

Ну и ещё наверное есть пара небольших изменений, о которых сейчас не помню.

Ищу удалённую работу

Могу предложить:

  • Common Lisp.

  • Python. Как веб-разработка (c Django знакомился, но предпочитаю Flask), так и разная логика. Имею опыт использования PyPy в продакшен. На github можно найти пример моего код на Python: python-closure-tempaltes.

  • JavaScript (предпочитаю JQuery, но также приходилось работать с ExtJS). В качестве демонстрации навыков могу предложить такой вот скринкаст: http://www.youtube.com/watch?v=f6b0sQpDGVM .

  • C++, правда, малость подзабыл - уже 4 года практически не писал, но до этого было 7 лет плотной практики с глубоким погружением в разные boost и т.п. В интернете завалялась одна моя небольшая старая библиотека с примером кода на C++: popen++. Кроме того, могу писать и на C, хотя и не очень люблю.

  • XSLT. В своё время написал на нём очень много кода.

  • Имею опыт работы с Oracle/PostgreSQL/MSSQL/Firebird, но сам специалистом по SQL не являюсь, обычно код для БД писали специально обученные люди.

  • Работал с MongoDB и RabbitMQ.

Более 10 лет занимаюсь разработкой программного. Последние полтора года работаю удалённо (правда, регулярно посещаю офис) в Магнит (Тандер) в должности главного специалиста (в основном, выполняю роль архитектора).

Географически нахожусь в Краснодаре и возможности переезда в другой город не имею (по семейным обстоятельствам).

Контакты:

E-mail:archimag at gmail.com
Skype:archimag-dev

Мой боекомплект веб-разработчика на Common Lisp

  • Hunchentoot - всё ещё лучший веб-сервер на Common Lisp. Работает по схеме "поток на соединение". Надеюсь в будущем у нас будут более современные веб-сервера. В принципе, у меня есть кое-какие наработки для работы под управлением Mongrel2, но на практике я пока использую исключительно Hunchentoot.

  • RESTAS - мой веб-фреймвок, делающий основной акцент на контролёре и повторном использовании кода. Из фреймворков на других языках наиболее близок к Flask (просто удивительно близок если учесть, что основную работу над RESTAS я выполнил в то время, когда ещё не знал о существовании Flask).

  • cl-closure-template - моя библиотека шаблонов, реализация спецификации Google Closure Templates.

  • cl-who - иногда использую её для небольших тестовых примеров, никогда для настоящих приложений.

  • cl-sanitize - моя библиотека для очистки HTML от нежелательного содержимого (совершенно необходима, если пользователям дозволяется вводить html-данные). Вдохновлена Sanitize, откуда были взяты whitelists и тестовые данные.

  • cl-data-forms - моя библиотека для обработки и валидации форм. Вдохновлена WTForms, но сильно от неё отличается.

  • postmodern - заслуженная и уважаемая библиотека для доступа к PostgreSQL, можно даже сказать, что эталонная библиотека для работы с СУБД в мире CL.

  • mongo-cl-driver - моя библиотека для работы c MongoDB. Не сказать, что бы она сильно функциональна, но мне пока хватает. Гораздо раньше появилась cl-mongo, но я считаю её совершенно уродливой как снаружи, так и внутри. Кроме того, mongo-cl-driver умеет работать асинхронно, хотя мне это ещё и не пригодилось (и скорей всего из-за этого дизайна далёк от совершенства).

  • colorize - библиотека для подсветки исходного кода, поддерживает не очень много языков, зато имеет совершенно уникальную поддержку для Common Lisp: превращает все стандартные функции, классы, переменные в ссылки на HyperSpec.

  • cl-docutils - библиотека для обработки разметки в формате reStructuredText. На мой взгляд сильно раздута и малость запутана, но это лучшее, что есть в этом классе для Common Lisp. Другой неплохой библиотекой для обработки языка разметки является cl-markdown, но я не люблю Markdown.

  • local-time - мощная и удобная библиотека для работы со временем.

  • ironclad - мощная и удобная криптографическая библиотека.

  • cl-pdf и cl-typesetting - генерация PDF. Без документации, но с несколькими примерами. Довольно удобные библиотеки, не лишенные своих глюков и особенностей. Я имею форк cl-pdf, который содержит несколько патчей, проигнорированных в основном списке рассылки, самый главный - непосредственная поддержка TTF-шрифтов.

  • salza2 и zip - работа с архивами.

  • cl-sphinx - моя библиотека для создания документации, реализующая некоторые возможности Sphinx.

  • cl-libxml2 - моя первая библиотека. Собственно, с её написания и началось моё глубокое знакомство с CL. Поскольку это был первый опыт, то дизайн, тот ещё. Ну и ресурсами надо управлять вручную. Зато может почти всё, что предлагает libxml2. Даже можно писать свои расширения для XSLT-процессора.

Ещё одна библиотека - cl-data-forms

При разработке веб-приложений есть такая неприятная вещь, как обработка форм. Вроде бы довольно тривиально, но без системного подхода превращается в какой-то мрак. Я давно облизывался на WTForms и вот теперь имею подходящее решение - cl-data-forms.

В принципе, cl-data-forms может быть использована не только для веб, никаких заточек под это там нет, но разработкой других приложений с пользовательским интерфейсом я пока на CL не занимаюсь.

Прежде, чем рассказывать про использование cl-data-forms, необходимо сказать несколько слов про data-sift. В принципе, я уже несколько раз упоминал эту библиотеку (и даже успел её использовать в RESTAS), но очень мало, плюс я несколько её переработал.

Там, где есть взаимодействие компьютера с человеком на основе текстовых форматов, возникает проблема преобразование внутренних представлений в текстовый, понятный для человека вид, и обратно. Мне очень понравилась библиотека cl-data-format-validation, призванная упростить эту проблему. Но с этой библиотекой есть одна проблема - она распространяется под лицензией GPL v3. Собственно, эта и подтолкнуло меня меня к созданию альтернативного решения под более мягкой лицензией.

data-sift определяет две обобщённые функции:

  • compile-parse-rule (rule &key &allow-other-keys)

  • compile-render-rule (rule &key &allow-other-keys)

compile-parse-rule принимает на вход правило, описывающее тип значения, и создаёт на его основе замыкание, которое может использоваться для валидации и, возможно, трансформации текстового значения, compile-render-rule делает совершенно обратное, т.е. возвращает функцию, которая может превратить значение в текст. Использовать это можно, например, так:

  1. (funcall (data-sift:compile-parse-rule 'integer :min-value 0 :max-value 100) "56")

data-sift уже включает в себя поддержку нескольких форматов данных (правда, всё это пока довольно сыро), а в своём приложении можно определить дополнительные форматы, специфичные для данного приложения. Например, так можно определить формат date, соответствующий элементу <input type="date" /> из HTML5:

  1. (defmethod data-sift:compile-parse-rule ((rule (eql 'date)) &key)
  2.   (alexandria:named-lambda date-parser (str)
  3.     (handler-case
  4.         (local-time:parse-rfc3339-timestring str :allow-missing-time-part t)
  5.       (error ()
  6.         (data-sift::vfail "Invalid date")))))
  7. (defmethod data-sift:compile-render-rule ((rule (eql 'date)) &key)
  8.   (alexandria:named-lambda date-renderer (date)
  9.     (local-time:format-timestring nil date :format local-time:+rfc3339-format/date-only+)))

Теперь можно вернуться к cl-data-forms. Сразу буду показывать код:

  1. (define-form-class user-info-form ()
  2.   ((username
  3.     :initarg |username|
  4.     :initform nil
  5.     :required "Username is required"
  6.     :label "Username")
  7.    (email
  8.     :initarg |email|
  9.     :initform nil
  10.     :stype data-sift:email
  11.     :required t
  12.     :label "Email Address")))
  13. (define-form-class user-password-form ()
  14.   ((password
  15.     :initarg |password|
  16.     :initform nil
  17.     :required "Password is required"
  18.     :stype (string :min-length 6 :message "Password must be at least 6 characters.")
  19.     :label "Password"
  20.     :itype "password")
  21.    (confirm-password
  22.     :initarg |confirmPassword|
  23.     :initform nil
  24.     :itype "password"
  25.     :label "Confirm password")))
  26. (define-form-class registration-form (user-info-form user-password-form)
  27.   ((birthday
  28.     :initform nil
  29.     :initarg |birthday|
  30.     :stype date
  31.     :label "Birthday"
  32.     :itype "date")
  33.    (keep-me-signed
  34.     :initform nil
  35.     :initarg |keepMeSigned|
  36.     :label "Keep me signed-in on this computer."
  37.     :itype "checkbox")))

Здесь определяется три формы:

  • user-info-form - предназначена для ввода имени пользователя и email

  • user-password-form - предназначенная для ввода пароля и его подтверждения

  • registration-form - включает в себя user-info-form и user-password-form, а также два дополнительных поля - день рождения и флаг "запомнить меня на этом компьютере"

Каждый макрос define-form-class создаёт новый класс. При описании слотов:

  • Можно использовать все те же самые параметры, что и при обычном defclass.

  • Параметр :initarg в описании слота является обязательным - он используется в последующем для получения данных формы. В коде выше вместо стандартных keyword-ов я использовал экранированные символы - так получается более красивый HTML (об этом ниже).

  • Параметр :requried используется для указания того, что поле является обязательным. Если указана строка, то она будет использоваться для создания сообщения об ошибке.

  • В параметре :stype можно указать формат для библиотеки data-sift и на его основе будет происходить проверка и преобразование данных.

  • Дополнительно можно указать любые другие параметры (в коде выше это :label и :itype) - они никак не обрабатываются, а просто сохраняются и могут быть использованы произвольным образом в зависимости от потребностей приложения.

Вот скриншот, полученный на основе registration-form (поскольку использован HTML5, то не во всех браузера поле для ввода даты будет именно таким, я использовал Chromium):

Скриншот HTML формы

Объекты форм можно создавать:

  • С помощью стандартного make-instance

  • С помощью функции make-form, которая принимает имя класса формы и набор параметров в формате alist (как post-параметры в Hunchentoot). При этом, сопоставление параметров слотам производится на основе параметра :initarg, указанного при описании слота.

Проверка значения слота производится при каждом его изменении с помощью setf slot-value в том случае, если для задания нового значения используется строка. В случае ошибки валидации исключение не возбуждается (подавляется), а сообщение сохраняется во внутренней структуре формы, его можно получить с помощью field-error.

Важный момент, cl-data-forms не имеет никаких функций для генерации HTML. Вместо этого, она позволяет добавить в описание слота любые произвольные данные, которые могут быть получены вместе со значением и сообщением об ошибке при вызове функций: form-data-alist и form-data-plist (разница между ними только в формате). Например:

  1. EXAMPLE> (data-forms:form-data-alist (make-instance 'user-info-form '|username| "Andrey" '|email| "fake"))
  2. ((|username| (:VALUE . "Andrey")
  3.              (:LABEL . "Username"))
  4.  (|email| (:ERROR . "Doesn't look like a valid email.")
  5.           (:VALUE . "fake")
  6.           (:LABEL . "Email Address")))

На основе такого описания приложение может создавать HTML в своём собственном стиле. Для теста я использовал специальный шаблон для cl-closure-template.

Быстро узнать корректно ли заполнена форма можно с помощью функцию is-valid.

Полный код примера здесь , а то я и так уже слишком много написал. Для работы этого примера необходимы самые последние версии data-sift, restas, cl-closure-template и cl-data-forms.

P.S. Если посмотреть на описание формы, то видно, что оно полностью декларативное (за исключением пары нюансов). А значит, можно пробовать на основе такого описания генерировать JavaScript код для того, что проводить модные проверки валидности данных ещё на клиенте.

Документация для cl-closure-template

Накидал черновой вариант русской документации по cl-closure-template, смотреть здесь . Но лучше не просто смотреть, а прям там же комментировать что не понятно, что не раскрыто, может где вообще соврал.

Наполовину это перевод документации на Google Closure Templates, местами несколько сокращённый, дабы не сказать лишнего. Остальная часть специфична именно для cl-closure-template. Особо рекомендую раздел Использование с Common Lisp .

Документация ещё будет доводиться до ума, но это скорей стилистическая работа, фактический материал будет меняться мало (надеюсь). Когда русский вариант будет окончен, будет перевод на английский.

Вообще, писать документацию совершенно адская работа (ибо не доставляет) и, что-бы как-нибудь разнообразить это дело, я попутно реализовал такую классную вещь, как Injected Data. Реализация оказалась довольно тривиальной, а вот профита от этой возможности действительно много.

ext-blog и тема iSimple

В Quicklisp есть пакет ext-blog, основанный на RESTAS. После последних изменений в RESTAS он, конечно, перестал работать и я решил его починить и сделать Pull Request автору. Ну заодно и посмотрел в работе этот движок.

Движок довольно забавный и в нём есть две темы, портированные с WordPress. Одну из этих тем (iSimple) я решил добавить к arblog и временно изменил оформление данного блога на эту тему. Пусть побудет так до следующего поста.