Генератор кода для Haskell

Некоторое время назад я решил поэкспериментировать с микросервисами на Haskell. Архитектура проекта подразумевает создание большого количества микро-проектов, каждый из которых отвечает за один небольшой объем задач. Через какое-то время мне надоело создавать эти проекты вручную, а также писать шаблонный код для каждой сущности из БД. Для решения этой проблемы я разработал небольшую утилиту, позволяющую генерировать шаблонный код. Под катом — подробнее.imageВпринципе, генераторов кода кругом — пруд пруди, но все они специфические и, как правило, предназначены для одного конкретного языка или даже проекта на этом языке. Конечно, посмотрев, как легко можно генерить код для сущностей, скажем, в Ruby on Rails, начинаешь рубистам реально завидовать. Среди пакетов Haskell я нашел всего два более-менее стоящих генератор кода: Hi и Haskeleton.

Hi умеет генерировать код целого проекта, что очень круто. Но вот эта маленькая ложка меда щедро разбавлена бочкой дегтя. Для того, чтобы сгенерировать код проекта, нужно передать Hi ссылку на URL шаблона проекта, которую еще надо где-то найти. Каждый раз Hi будет скачивать шаблоны по этой ссылке, вместо того, чтобы закешировать скачанное. Hi поддерживает всего несколько переменных для подстановки: package-name, moduleName, author, email, а это уже полный фейл.

Haskeleton — это навороченный шаблон проекта для Hi, очень хорошо продуманный. Он даже имеет в своем составе утилиту Haskeleton.hs, позволяющую создавать пустые модули и вставлять импорты этих модулей во все подходящие места. Однако пустые модули — это не то, что мне нужно.

Требования, которые я предъявлял к генератору кода, были следующие:

Глобальный и локальный репозитории. Хотелось бы скачивать шаблоны один раз или изредка, но точно не каждый раз, когда что-нибудь генеришь. Еще хотелось бы иметь возможность создавать свои шаблоны в локальном репозитории без необходимости закачивать их куда-то наружу. Возможность генерировать шаблонный код сущностей. Например, для таблицы Article сгенерить весь код, который умеет извлекать/cохранять данные в эту таблицу, представлять её в JSON формате и т.д. — короче, генерить код модели. Возможность генерировать код проекта целиком, как это умеет Hi. В процессе генерации кода проекта иметь возможность использовать произвольные шаблоны, как это не умеет Hi. Т.е. использовать не только заданные заранее переменные, а любые, в том числе и вложенные объекты, и списки. Исходя из этих требований и наличия готовых библиотек на Haskell я выбрал пакет hastache для шаблонного кода на основе синтаксиса Mustache, а также aeson для того, чтобы параметры к шаблону можно было передать в формате JSON.Локальный репозиторий шаблонов находится в директории ~/.trurl/repo, туда складываются все шаблоны сущностей и проектов, скачанные из глобального репозитория. Шаблон проекта — tar-архив. Шаблон сущности — просто файл. Например, пусть там есть файл myentity.hs со следующим шаблонным кодом:

data {{entityName}} = {{entityName}}{{#props}} {{type}}{{/props}}

insert{{entityName}} :: Pool Connection → Maybe {{entityName}} → ActionT Text IO () insert{{entityName}} _ Nothing = return () insert{{entityName}} pool (Just ({{entityName}} _{{#props}} {{name}}{{/props}})) = do liftIO $ execSqlT pool [{{#props}}(show {{name}}){{^last}}, {{/last}}{{/props}}] «INSERT INTO {{entityName}}({{#props}}{{name}}{{^last}}, {{/last}}{{/props}}) VALUES ({{#props}}?{{^last}},{{/last}}{{/props}})» return () Запустим trurl со следующими параметрами: trurl new Article myentity '{«entityName»: «Article», «props»:[{«name»: «title», «type»: «String»},{«name»: «body», «type»: «String»},{«name»: «year», «type»: «Integer», «last»: true}]}'

В результате получим следующий код:

data Article = Article String String Integer

insertArticle: Pool Connection → Maybe Article → ActionT Text IO () insertArticle _ Nothing = return () insertArticle pool (Just (Article _ title body year)) = do liftIO $ execSqlT pool [(show title), (show body), (show year)] «INSERT INTO Article (title, body, year) VALUES (?,?,?)» return () Аналогично можно генерить и шаблонные проекты при помощи команды create. Правда здесь надо придерживаться следующих правил: Все файлы, которые необходимо переименовать в соответствии с именем проекта, должны иметь имя «ProjectName». Все файлы шаблонов mustache в шаблонном проекте должны иметь расширение ».template». В mustache-шаблоны можно передавать любые JSON-параметры, также, как и в шаблоны отдельных файлов, создаваемых командой new. При этом как минимум один параметр будет всегда доступен, даже если не передать JSON вообще. Это строка ProjectName, содержащая имя проекта. Для того, чтобы скачать все доступные шаблоны из глобального репозитория необходимо выполнить команду «trurl update».При помощи команды «trurl list» можно получить список всех доступных шаблонов.

Команда «trurl help [template]» покажет подсказку о том, как пользоваться указанным шаблоном.

Команда «trurl new [newfilename] [file_template] [json_params]» предназначена для генерации ровно одного файла.

Команда «trurl create [newprojname] [project_template] » предназначена для генерации целого проекта, JSON-параметры здесь не обязательны.

На данный момент для trurl в глобальном репозитории доступны всего несколько шаблонов. Это шаблоны для моих микро-сервисов (scotty-mysql и scotty-entity), и haskeleton, который я добавил по просьбе одного из пользователей trurl. Вы можете создавать свои шаблоны, просто пришлите мне pull-request в github.com/dbushenko/trurl/tree/master/devrepo, и я добавлю их в главный репозиторий.

Проект trurl молодой и потому не лишен недостатков. Не всегда удобно передавать длиннющие JSON-строки для генерирования сущностей, не любой шаблон можно сгенерировать при помощи mustache, не очень удобно устроен главный репозиторий шаблонов. Все это — направления для улучшений, которыми я активно занимаюсь и буду рад, если ко мне присоединятся и другие.

И последнее. Название trurl я выбрал в честь космического конструктора Трурля из произведений Станислава Лема «Семь путешествий Трурля и Клапауция», именно портрет Трурля вы видели в начале статьи.

© Habrahabr.ru