Сайт вебмастера

Простой генератор документации на PHP CLI с автоматизацией через Gulp

22-07-2020Время чтения ~ 7 мин.PHP 5679

Генератор мне потребовался для создания документации для новой версии Berry CSS. Но перед этим я хочу сделать небольшой анонс. Я о нём уже писал в своём телеграмм-канале, продублирую и здесь.

Я решил расширить возможности Berry и сделать из него полноценный css-фреймворк на основе утилитарных классов. Основная задача - повторить Bootstrap по возможностям, за исключением сложных js-компонентов. Отличие Berry в том, что утилитарными классами можно выполнить вёрстку практически любого блока, то есть это универсальный подход. Но, чтобы показать его преимущество, нужна полноценная документация. Поэтому Berry 3 (будущая версия) выйдет только после того, как я напишу всю документацию.

Структурно Berry будет состоять из нескольких модулей. Первый — утилитарные классы, это примерно то, что сейчас и есть. Второй модуль — типографика. Это примерно то, что входит в комплект UniCSS. Третий модуль — цвета. Я сильно их переиграл, чтобы можно было работать не только с индивидуальными цветами, но и целыми группами. Например, если шаблон использует только красные оттенки, то достаточно указать только эту группу. И последнее — будет несколько js-компонентов на чистом javascript без каких-либо зависимостей. Меня, честно говоря, уже задолбало учитывать версии jQuery, и прочих библиотек. Такие компоненты состоят из css, scss и js-файлов + readme и всё. Подключай как хочешь.

Для новой версии Berry я сделал приватный репозиторий и приглашаю тех, кто хотел бы поучаствовать в разработке. Это тестирование, замечания и предложения. Секретного ничего нет, но рабочий процесс не хочу выставлять напоказ. После всех работ я обновлю общедоступный репозиторий Berry. Если вы хотите поучаствовать, то напишите мне на почту и укажите свой ник или email на GitHub — я вышлю приглашение.

Теперь возвращаемся к php-генератору. :-)

Структура каталогов будет такой:

doc/ — здесь и хранятся все файлы
   _src/ — это каталог исходных файлов
       engine/ — каталог с функциями генератора
          generation.php — этот файл запускаем для генерации
          
       layout/ — здесь шаблон html-разметки
          main.php — основной файл
          menu.php — блок меню
       
       gulp/ — файлы для Gulp
 
       index.php — это файлы контента каждой страницы
       first.php
       second.php
       ...
   
   assets/berry.css — это каталог для стилей, картинок и js-скриптов
   
   index.html — все html-файлы будут генерироваться автоматом
   first.html
   second.html
   ...

То есть все исходные файлы будут храниться в каталоге _src, а после генерации будут помещаться в корень doc и оттуда и будут запускаться как обычные html-файлы.

Файл generation.php можно запустить и через браузер, но лучше через командную строку (CLI):

php generation.php

Естественно PHP должен быть доступен: если Windows выдаёт, что команда php не найдена, то укажите полный путь к php.exe, либо пропишите путь в системной переменной PATH (гуглите, если не знаете как это сделать). Скрипт пробежится по всем исходным файлам и создаст из них html-файлы.

В исходном файле любой страницы нужно будет указывать массив данных. Вот пример для index.php

<?php if (!defined('DOCGEN')) exit('No direct script access allowed');
 
// все данные для этой страницы
$data = [
    'title' => 'Привет!',
    'description' => 'Какое-то описание',
    'file' => 'index.html', // выходной файл
];
?>
 
<h1>Привет!</h1>

<p class="t120 mar20-tb">Здесь располагается текст страницы</p>
и т.д.

Первая строчка — это защита от прямого вызова. Константа DOCGEN определяется в generation.php.

Дальше идёт массив $data, где описываются параметры страницы. И дальше идёт обычный текст с html-разметкой.

Выходной файл формируется на основе main.php. Приведу его в сокращённом варианте:

<?php if (!defined('DOCGEN')) exit('No direct script access allowed'); ?>
<!DOCTYPE HTML>
<html lang="ru">
<head>
	<meta charset="UTF-8">
	<title><?= htmlspecialchars($data['title'], ENT_QUOTES) ?></title>
	<meta name="description" content="<?= htmlspecialchars($data['description'], ENT_QUOTES) ?>">
	<link rel="stylesheet" href="<?= $data['assetsDir'] ?>berry.css">
</head>
<body>
...
<div class="layout-center-wrap"><div class="layout-wrap">
     <div><?= $menu ?></div>
     ...
     <div><?= $content ?></div>
</div></div>
...
</body></html>

В секции HEAD выводятся данные из $data. Контентная часть (из исходного php-файла) попадает в переменную $content и выводится. А меню аналогичным образом выводится через $menu. Эти переменные формируются автоматом в generation.php.

Файл menu.php отвечает за вывод меню. Вообще у меня меню формируется через массив, но там получается достаточно сложный код, поэтому для простоты можно считать, что в этом файле создается html-разметка для боковой колонки с меню.

<?php if (!defined('DOCGEN')) exit('No direct script access allowed'); ?>
 
<ul>
	<li><a href="index.html">Главная</a></li>
	<li><a href="first.html">First</a></li>
	<li><a href="second.html">Second</a></li>
</ul>

Теперь рассмотрим как устроен generation.php.

#!/usr/bin/env php
<?php
 
/*    запуск генерации всех файлов через командную строку php cli
        php generation.php
*/
 
define('DOCGEN', 1); // для защиты от прямого запуска php-файлов 
 
// если какой-то ключ совпадает, то его можно не указывать в файле страницы
$dataDefault = [
    'title' => '',
    'description' => '',
    'file' => 'output.html', // выходной файл
    'assetsDir' => 'assets/', // каталог для assets относительно doc/
];
 
$engineDir = __DIR__ . DIRECTORY_SEPARATOR;
$srcDir = dirname($engineDir) . DIRECTORY_SEPARATOR;
$docDir = dirname($srcDir) . DIRECTORY_SEPARATOR;
 
$srcFiles = glob($srcDir . '*.php');
 
foreach($srcFiles as $fn) {
    ob_start();
    require $fn;
    $content = ob_get_contents();
    if (ob_get_length()) ob_end_clean();
    
    $data = array_merge($dataDefault, $data);
    
    ob_start();
    require $srcDir . 'layout/menu.php';
    $menu = ob_get_contents();
    if (ob_get_length()) ob_end_clean();
    
    ob_start();
    require $srcDir . 'layout/main.php';
    $main = ob_get_contents();
    if (ob_get_length()) ob_end_clean();   
        
    file_put_contents( $docDir . $data['file'], $main);
    
    echo $data['file'] . " - OK\n";
}
 
# end of file

Первая строчка нужна для того, чтобы обеспечить корректный запуск файла через командную строку PHP. Строго говоря она не нужна для запуска из-под Windows, но обычно её всегда прописывают.

Дальше мы определяем константу DOCGEN, для того, чтобы заблокировать прямое обращение к php-файлам. Это «стандартная» защита.

Массив $dataDefault содержит дефолтные данные для $data. Это на случай, если в файле страницы не указан какой-то параметр и скрипт не вывалился с ошибкой. А так мы их сливаем между собой.

Дальше мы определяем каталоги. Обратите внимание, что считаем от каталога текущего файла с помощью __DIR__. А дальше с помощью dirname() поднимаемся на уровень выше.

Получаем список файлов в $srcFiles с помощью стандартной glob() и запускаем по ним цикл foreach.

В цикле мы подключаем вначале основной файл страницы в режиме буферизации вывода. Результат записываем в $content — ту самую которая выводится в main.php. При этом у нас должна появиться переменная $data (из файла страницы), которую мы смерживаем с дефолтным массивом.

После этого подключаем аналогичным образом меню (menu.php) и в завершении всё это дело отдаём в main.php. Переменная $main содержит уже готовый html-код страницы, который мы просто сохраняем как файл.

И в завершении цикла выводим сообщение в консоль о результате операции.

Вы можете скачать полный комплект, чтобы посмотреть как это работает.

Прикручиваем Gulp

Постоянно запускать генерацию через консоль не очень удобно, поэтому можно использовать Gulp, который будет отслеживать все изменения в каталоге _src и автоматом запускать generation.php. Нужно будет только F5 в браузере нажать, хотя наверное можно и live reload подключить при желании.

Gulp работает через Node.js, поэтому убедитесь, что он уже установлен.

После этого нужно поставить сам Gulp через npm. Всё это приведено на сайте Gulp'а, поэтому не буду дублировать.

Дальше так. Сделаем каталог doc/_src/gulp в котором разместим нужные файлы. Вначале кинем package.json

{
  "name": "doc generation",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "gulp": "^4.0.2"
  }
}
На самом деле, каталог для Gulp может находиться где угодно. Размещение в _src я привожу как пример.

Наберём в консоли, чтобы инициализировать зависимости:

npm i

Создадим generation.bat:

php "d:\domains\doc\_src\engine\generation.php"

Этот файл будет запускаемым. Укажите только свой путь к generation.php. Если просто запустить этот батник, то произойдёт обновление html-файлов документации.

Теперь создадим gulpfile.js, который будет использовать Gulp для свой работы.

var gulp = require('gulp');
var exec = require('child_process').exec;
 
gulp.task('generation', function (cb) {
    exec('generation.bat', function (err, stdout, stderr) {
        console.log(stdout);
        console.log(stderr);
        cb(err);
    });
});
 
gulp.task('watch', function() {
	gulp.watch('d:/domains/doc/_src/*.*', gulp.parallel('generation'));
});

Здесь мы указываем что нужно отслеживать (watch) каталог _src. В случае изменений, запустится задача (task) generation, которая в свою очередь запустит generation.bat.

Обратите внимание, что в путях Windows нужно заменить обратные слэши на прямые.

После этого в консоли запускаем отслеживание:

gulp watch

Должно появится сообщение:

[13:37:59] Using gulpfile d:\domains\doc\_src\gulp\gulpfile.js
[13:37:59] Starting 'watch'...

А при каждом изменении исходных файлов сообщение:

@ d:\domains\doc\_src\gulp
$ php "d:\domains\doc\_src\engine\generation.php"
first.html - OK
index.html - OK
second.html - OK

Главное — это перед запуском Gulp поменяйте пути к файлам. Готовый комплект доступен в скачанном архиве.


Сам скрипт очень простой и без труда расширяется. Можно формировать меню на основе массива; для разметки я использую Simple из MaxSite CMS, а также минификацию html-кода. То есть вариантов расширения генератора довольно много.

Похожие записи