Работая над предыдущей статьей, мы столкнулись с очень интересной программой. Эта программа называется stagit и предназначается для создания набора веб-страниц из существующего git-репозитория. Stagit — очень компактная программа в стиле suckless-tools, написанная на C, что делает её очень компактной и быстрой. Это обстоятельство позволяет собрать программу, к примеру, на Raspberry Pi и получить в свое распоряжение минималистичный веб-интерфейс своих рабочих репозиториев. Сегодня мы расскажем как с помощью stagit и D получить в свое распоряжение такой интерфейс.
Программа stagit служит генератором html-страниц на основе репозитория git, это значит, что её с помощью мы фактически получаем в свое распоряжение почти готовый статический сайт. Однако, у stagit есть ряд недостатков: программа служит только генератором страниц, но не имеет веб-сервера в своей поставке, генерирует набор страниц только для одного репозитория.
Сначала разберемся со второй проблемой, а именно, научимся генерировать html-страницы для всех репозиториев в пределах одной директории. В этом случае, мы предполагаем, что пользователь хранит все свои репозитории в некотором каталоге, который и будем обрабатывать с помощью stagit. До того, как мы начнем обрабатывать директорию, необходимо собрать stagit из исходных кодов следующим образом:
git clone git://git.codemadness.org/stagit
cd stagit
make
Для того, чтобы сгенерировать статические странички репозиториев с помощью stagit, мы будем использовать специально подготовленный скрипт на D, который выглядит следующим образом:
import std.algorithm; import std.file; import std.path; import std.process; import std.range; import std.stdio; import std.string; auto walkRepositories(string repositories, string pages) { alias isRepositoryDirectory = delegate (a) { return ( a.isDir && buildPath(a, ".git").exists ); }; return repositories .dirEntries(SpanMode.shallow) .filter!isRepositoryDirectory; } auto processRepositories(string stagit, string repositories, string pages) { alias createPagesPath = delegate (a) { return buildPath(pages, baseName(a)); }; alias createSymlinks = delegate (a) { try { symlink(format(`%s/favicon.png`, pages), `favicon.png`); symlink(format(`%s/logo.png`, pages), `logo.png`); symlink(format(`%s/style.css`, pages), `style.css`); } catch (Throwable) {} return; }; alias generateGitPage = delegate (a) { auto workingDir = createPagesPath(a); if (!workingDir.exists) { mkdir(workingDir); } chdir(workingDir); createSymlinks(a); auto stagitCommand = executeShell( format(`%s/stagit -c .cache %s`, stagit, a) ); if (stagitCommand.status == 0) { writefln(`Repository in %s processed`, a); } }; auto allPaths = ""; repositories .walkRepositories(pages) .tee!(a => (allPaths ~= format(`%s `, a))) .each!generateGitPage; chdir(pages); auto stagitIndexCommand = executeShell( format(`%s/stagit-index %s > index.html`, stagit, allPaths) ); if (stagitIndexCommand.status == 0) { writeln(`index.html for repositories generated`); } } void main() { enum string REPOSITORIES = ``; enum string PAGES = ``; enum string STAGIT = ``; processRepositories(STAGIT, REPOSITORIES, PAGES); }
Данный скрипт использует две собранные из репозитория программы: stagit и stagit-index. Про первую программу мы уже рассказали, а вот работа второй требует пояснений: stagit-index генерирует самый главный файл – index.html, в котором будут содержаться ссылки на страницы репозиториев. Для скрипта достаточно указать следующие параметры: директорию, где находятся репозитории (переменная REPOSITORIES
); директорию, где будут находиться файлы html под каждый репозиторий (переменная PAGES
, директория указывается общая, но под каждый репозиторий будет создана своя поддиректория) и путь к директории, где находятся собранные stagit и stagit-index.
Скрипт состоит из двух функций walkRepositories
и processRepositories
. Первая функция генерирует диапазон с путями к репозиториям. Её осуществим с помощью использования литерала функционального делегата isRepositoryDirectory
в совокупности с проходом по содержимому заданной директории, для чего применим dirEntries
(список содержимого директории) и filter
. Делегат isRepositoryDir
использует предикат isDir
, который проверяет является ли элемент содержимого директорией, и проверяет наличие скрытой директории .git внутри каталога (эта особенность со скрытой директорией типична для каталогов Git). Функция walkRepositories
принимает два параметра: путь к директории с репозиториями и путь к директории, в которой будут сгенерированные файлы html.
Вторая функция processRepositories
принимает три параметра: путь к директории с собранным stagit, путь к директории с репозиториями и путь к директории с будущими html-страницами. Она задействует ряд вспомогательных функций, которые также описаны как литералы функциональных делегатов, это функции:
createPagesPath
– строит путь до директории с html-файлами для одного из репозиториев, аргументом служит путь (предполагается, что это путь до отдельного репозитория);createSymlinks
– создает символические ссылки на три файла: favicon.png, logo.png и style.css. Ссылки создаются для того, чтобы не копировать эти файлы в каждую директорию со сгенерированными страницами, это экономит место на диске. Создание символических ссылок обернуто в обработку исключений, так как, при повторном запуске скрипта (это случай обновления данных по репозиториям), если ссылки уже существуют, будет сгенерировано исключение. Чтобы не останавливать последующую пакетную обработку директорий и была применена обертка.generateGitPage
– выполняет генерацию страниц для одного репозитория с помощью stagit. Происходит это следующим образом: сначала с помощьюcreatePagesPath
конструируется путь для директории под будущие веб-страницы, это путь до так называемой “рабочей директории” (workingDirectory), и если рабочая директория не существует по сконструированному пути, то она создается. После этого, происходит смена текущего пути на путь до рабочей директории (функцияchdir
), запускается генерация символических ссылок (createSymlinks
) и выполняется вызов stagit c помощью командной оболочки Linux черезexecuteShell
. После выполнения команды, если она успешно отработала (в этом случае ее статус равен 0, код статуса точно такой же, как и в программах на C), выводится сообщение о том, что папка обработана.
Основная же функция работает с помощью цепочки UFCS-вызовов, которая начинается с одного аргумента, предполагающего путь до директории с репозиториями. Перед выполнением всей цепочки создаем строковую переменную allPaths
, которая сохранит впоследствии пути ко всем репозиториям в виде строки и эту переменную мы будем также использовать в цепочке. Сначала в цепочке генерируются все пути к репозиториям с помощью walkRepositories
, затем происходит разветвление с помощью алгоритма tee
, который позволяет выполнить некоторую работу над диапазоном, не меняя его и работает аналогично команде tee
из Unix. В нашем случае, tee
выполнит аккумулирование путей репозиториев в строковой переменной; данное действие необходимо по той простой причине, что сохранение результата walkRepositories
в переменную не представляется возможным: это ленивый диапазон, а после окончания своей работы он будет просто опустошен. Дальнейшие действия просты: с помощью createGitPage
и алгоритма each
обрабатывается каждый репозиторий, затем осуществляется смена рабочей директории на директорию, где расположены статические страницы и точно таким же образом, как было со stagit запускается stagit-index и, если выполнение команды завершается успешно, будет выведено сообщение о том, что файл index.html создан.
Остается только добавить в переменные свои пути и запустить файл скрипта с помощью rdmd или добавить в файл shebang, после чего просто запустить на выполнение. Однако, перед запуском скрипта настоятельно рекомендуем добавить в директорию, где будут располагаться веб-страницы репозиториев, файлы favicon.png, logo.png (их можно скопировать из директории со stagit, если нет готовых) и скопировать из директории stagit файл style.css – это позволит получить более-менее приемлемую разметку страницы, а не голый html.
Но даже после успешного завершения работы скрипта мы не получим готовой системы, поскольку для обслуживания даже статичных файлов требуется веб-сервер. Тут можно было бы использовать зарекомендовавших себя nginx или apache, но для столь скромного и маленького проекта, это было бы черезчур. Поэтому сделаем собственный минималистичный веб-сервер, взяв за основу наш перевод документации по vibe-d и стандартный пример веб-сервера с официального сайта dlang, немного доработав его под обслуживание статических файлов:
#!/usr/bin/env dub /+ dub.sdl: name "dgit" dependency "vibe-d" version="~>0.8.2" +/ import std.stdio; import vibe.vibe; import vibe.http.fileserver; import vibe.http.router; import vibe.http.server; void index(HTTPServerRequest req, HTTPServerResponse res) { static import std.file; auto result = cast(string) std.file.read(`index.html`); res.writeBody(result, "text/html; charset=UTF-8"); } void main() { auto router = new URLRouter; router.get("/", &index); router.get("*", serveStaticFiles("")); auto settings = new HTTPServerSettings; settings.port = 8080; settings.bindAddresses = ["::1", "127.0.0.1"]; listenHTTP(settings, router); runApplication(); }
Веб-сервер оформлен в виде простого скрипта, который можно запустить из отдельного файла без создания полноценного проекта dub, запускать его требуется из той же директории, в которой находятся веб-страницы репозиториев, что обеспечит подхват индексного файла и всех остальных страниц. После запуска скрипта генерации страниц и простого веб-сервера должно получится что-то вроде этого:


Fun fact! Вы видите на главной странице интерфейса и странице отдельного репозитория фразу Unnamed repository; edit this file ‘description’ to name the repository.? Это значит что не задано описание и путь для клонирования репозитория. Дело в том, что stagit по умолчанию не извлекает эти данные, но предполагает, что их можно получить определенным образом. Для того, чтобы избавиться от “неименнованных репозиториев” достаточно в папке репозитория создать три файла:
- description – файл в котором содержится текстовое описание репозитория;
- url – файл, в котором содержится путь для клонирования (к примеру, git://git.codemadness.org/example);
- owner – файл в котором содержится имя владельца репозитория
Если все будет сделано правильно, то вы получаете свой собственный интерфейс к git-репозиториям, веб-сервер и скрипт генерации, html-страниц. В случае необходимости можно сделать публичный доступ и «прикрутить» доменное имя.