Статический сайт из репозиториев git с помощью stagit

Работая над предыдущей статьей, мы столкнулись с очень интересной программой. Эта программа называется 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.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-страниц. В случае необходимости можно сделать публичный доступ и «прикрутить» доменное имя.

aquaratixc

Программист-самоучка и программист-любитель

Добавить комментарий