@kirigami/php-prepros 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +622 -0
- package/index.d.ts +109 -0
- package/index.js +1 -0
- package/package.json +58 -0
- package/src/libraries/cache.class.php +101 -0
- package/src/libraries/fs.class.php +133 -0
- package/src/libraries/html.class.php +176 -0
- package/src/libraries/img.class.php +122 -0
- package/src/libraries/md.class.php +444 -0
- package/src/libraries/md.plugins.php +86 -0
- package/src/libraries/obf.class.php +23 -0
- package/src/libraries/prepros.class.php +157 -0
- package/src/libraries/prepros.plugins.php +28 -0
- package/src/libraries/std.class.php +28 -0
- package/src/libraries/str.class.php +47 -0
- package/src/libraries/yaml.class.php +453 -0
- package/src/prepros.js +107 -0
- package/src/prepros.php +45 -0
- package/src/utils/isbinary.js +79 -0
- package/src/utils/joinwith.js +12 -0
- package/src/utils.inc.php +29 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Maxime Larrivée-Roy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
# @kirigami/php-prepros
|
|
2
|
+
|
|
3
|
+
> PHP preprocessor for the **Kirigami** static site generator.
|
|
4
|
+
|
|
5
|
+
Build full static websites in PHP — with zero server, zero runtime dependency, zero compromise on expressiveness. Write your pages as regular PHP files, annotate them with a PHPDOC header, and let `php-prepros` compile everything to clean, deployable HTML.
|
|
6
|
+
|
|
7
|
+
Part of the **Kirigami** project ecosystem. Other packages are coming soon.
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@kirigami/php-wasm)
|
|
10
|
+
[](./LICENSE)
|
|
11
|
+
[](https://nodejs.org)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Table of contents
|
|
16
|
+
|
|
17
|
+
- [@kirigami/php-prepros](#kirigamiphp-prepros)
|
|
18
|
+
- [Table of contents](#table-of-contents)
|
|
19
|
+
- [How it works](#how-it-works)
|
|
20
|
+
- [Installation](#installation)
|
|
21
|
+
- [Configuration — `kirigami.yaml`](#configuration--kirigamiyaml)
|
|
22
|
+
- [Writing pages](#writing-pages)
|
|
23
|
+
- [PHPDOC header](#phpdoc-header)
|
|
24
|
+
- [Auto-loading data files](#auto-loading-data-files)
|
|
25
|
+
- [JavaScript API](#javascript-api)
|
|
26
|
+
- [`render(file?)`](#renderfile)
|
|
27
|
+
- [`sitemap()`](#sitemap)
|
|
28
|
+
- [PHP classes reference](#php-classes-reference)
|
|
29
|
+
- [PREPROS](#prepros)
|
|
30
|
+
- [`PREPROS::render(string $file)`](#preprosrenderstring-file)
|
|
31
|
+
- [`PREPROS::sitemap()`](#preprossitemap)
|
|
32
|
+
- [`PREPROS::exportFile(string $file)`](#preprosexportfilestring-file)
|
|
33
|
+
- [MD](#md)
|
|
34
|
+
- [Plugin API](#plugin-api)
|
|
35
|
+
- [HTML](#html)
|
|
36
|
+
- [YAML](#yaml)
|
|
37
|
+
- [CACHE](#cache)
|
|
38
|
+
- [IMG](#img)
|
|
39
|
+
- [FS](#fs)
|
|
40
|
+
- [STR](#str)
|
|
41
|
+
- [OBF](#obf)
|
|
42
|
+
- [STD](#std)
|
|
43
|
+
- [Plugin system](#plugin-system)
|
|
44
|
+
- [PREPROS tags](#prepros-tags)
|
|
45
|
+
- [PREPROS hooks](#prepros-hooks)
|
|
46
|
+
- [MD plugins](#md-plugins)
|
|
47
|
+
- [Built-in plugins](#built-in-plugins)
|
|
48
|
+
- [`{% callout type ["Title"] content %}`](#-callout-type-title-content-)
|
|
49
|
+
- [Extending the `<markdown>` tag](#extending-the-markdown-tag)
|
|
50
|
+
- [License](#license)
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## How it works
|
|
55
|
+
|
|
56
|
+
`@kirigami/php-prepros` runs your PHP source files inside a **WebAssembly PHP 8.x runtime** ([`@kirigami/php-wasm`](https://github.com/kirigami/php-wasm)), entirely in Node.js — no PHP installation required on the host machine.
|
|
57
|
+
|
|
58
|
+
The lifecycle of a page build looks like this:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
_index.php ──▶ PHP (wasm) ──▶ processTags() ──▶ HTML::format() ──▶ index.html
|
|
62
|
+
│
|
|
63
|
+
├── before.php (optional layout header)
|
|
64
|
+
├── after.php (optional layout footer)
|
|
65
|
+
└── PHPDOC annotations resolved (yaml / json / md / url)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Files are mounted into the WebAssembly virtual filesystem on demand. Only `.php`, `.json`, `.yaml`, `.md` and any extra extensions listed in `prepros.mountext` are mounted, keeping memory usage low.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Installation
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npm install @kirigami/php-prepros
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
> **Node.js ≥ 20** is required (ESM-only package).
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Configuration — `kirigami.yaml`
|
|
83
|
+
|
|
84
|
+
Every project **must** have a `kirigami.yaml` at its root. The preprocessor reads it at startup and throws if it is absent or invalid.
|
|
85
|
+
|
|
86
|
+
```yaml
|
|
87
|
+
kirigami:
|
|
88
|
+
root: src/ # Required. Source directory containing your _*.php pages.
|
|
89
|
+
baseurl: https://example.com # Used by sitemap generation.
|
|
90
|
+
sitename: My Website # Arbitrary key/value pairs injected as PHP variables.
|
|
91
|
+
author: Jane Doe
|
|
92
|
+
|
|
93
|
+
prepros:
|
|
94
|
+
before: _layout/header.php # Included before every page body.
|
|
95
|
+
after: _layout/footer.php # Included after every page body.
|
|
96
|
+
format: true # Pretty-print the HTML output (default: false).
|
|
97
|
+
network: false # Allow HTTP fetches in PHPDOC @tag annotations.
|
|
98
|
+
mountext: # Extra file extensions to mount into the wasm fs.
|
|
99
|
+
- .svg
|
|
100
|
+
- .txt
|
|
101
|
+
includes: # PHP files auto-included before page rendering.
|
|
102
|
+
- _lib/helpers.php
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The entire `kirigami` block is extracted into PHP variables and made available in every page template. `$sitename`, `$author`, etc. are available without any further setup.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Writing pages
|
|
110
|
+
|
|
111
|
+
Source pages live in the directory pointed to by `kirigami.root`. The naming convention is straightforward: any file whose name starts with `_` and ends in `.php` is treated as a page source. The leading underscore is stripped in the output filename.
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
src/
|
|
115
|
+
├── _index.php → src/index.html
|
|
116
|
+
├── about/
|
|
117
|
+
│ └── _index.php → src/about/index.html
|
|
118
|
+
└── blog/
|
|
119
|
+
├── _index.php → src/blog/index.html
|
|
120
|
+
└── _articles.yaml (data file, not compiled)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Directories whose name starts with `_` (e.g. `_layout/`, `_lib/`) are skipped entirely during directory-wide builds.
|
|
124
|
+
|
|
125
|
+
### PHPDOC header
|
|
126
|
+
|
|
127
|
+
Every page starts with a PHP docblock that drives metadata and data loading:
|
|
128
|
+
|
|
129
|
+
```php
|
|
130
|
+
<?php
|
|
131
|
+
/**
|
|
132
|
+
* @name about
|
|
133
|
+
* @title About us
|
|
134
|
+
* @abstract A short description of this page.
|
|
135
|
+
*/
|
|
136
|
+
?>
|
|
137
|
+
<section>
|
|
138
|
+
<h1><?php echo $title; ?></h1>
|
|
139
|
+
<p><?php echo $abstract; ?></p>
|
|
140
|
+
</section>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
All annotations are injected as PHP variables (`$name`, `$title`, `$abstract`, …). You can define any custom annotation you need.
|
|
144
|
+
|
|
145
|
+
Anotations are also avaiables as variables in `before` and `header` php included files so you can write proper metas in the HTML header.
|
|
146
|
+
|
|
147
|
+
### Auto-loading data files
|
|
148
|
+
|
|
149
|
+
When an annotation value looks like a filename (with a `.yaml`, `.yml`, `.json`, or `.md` extension), it is automatically parsed and injected as a structured variable instead of a plain string.
|
|
150
|
+
|
|
151
|
+
```php
|
|
152
|
+
<?php
|
|
153
|
+
/**
|
|
154
|
+
* @name medias
|
|
155
|
+
* @articles _articles.yaml
|
|
156
|
+
*/
|
|
157
|
+
?>
|
|
158
|
+
<?php foreach ($articles as $article): ?>
|
|
159
|
+
<a href="<?php echo $article->lien; ?>">
|
|
160
|
+
<?php echo $article->titre; ?>
|
|
161
|
+
</a>
|
|
162
|
+
<?php endforeach; ?>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
| Extension | Parsed as |
|
|
166
|
+
|-----------|-----------|
|
|
167
|
+
| `.yaml` / `.yml` | `stdClass` object (or array of objects for sequences) |
|
|
168
|
+
| `.json` | Result of `json_decode()` |
|
|
169
|
+
| `.md` | HTML string via `MD::toHtml()` |
|
|
170
|
+
|
|
171
|
+
When `network: true` is set in `kirigami.yaml`, annotation values that start with `http://` or `https://` are fetched from the network and parsed the same way:
|
|
172
|
+
|
|
173
|
+
```php
|
|
174
|
+
/**
|
|
175
|
+
* @posts https://api.example.com/posts.json
|
|
176
|
+
*/
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## JavaScript API
|
|
182
|
+
|
|
183
|
+
```js
|
|
184
|
+
import { render, sitemap } from '@kirigami/php-prepros';
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### `render(file?)`
|
|
188
|
+
|
|
189
|
+
Compile a single PHP page or a whole directory.
|
|
190
|
+
|
|
191
|
+
```js
|
|
192
|
+
// Compile one page
|
|
193
|
+
const result = await render('about/_index.php');
|
|
194
|
+
|
|
195
|
+
// Compile everything under src/
|
|
196
|
+
const result = await render('.');
|
|
197
|
+
|
|
198
|
+
// Compile everything (uses kirigami.root from config)
|
|
199
|
+
const result = await render();
|
|
200
|
+
```
|
|
201
|
+
> Path use by `render()` are all relative to `kirigami.root` configuration.
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
**Returns** `Promise<PreprosResult>`:
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
interface PreprosResult {
|
|
208
|
+
success: boolean;
|
|
209
|
+
files: string[]; // relative paths of every file written
|
|
210
|
+
error?: string; // present only on failure
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### `sitemap()`
|
|
215
|
+
|
|
216
|
+
Generate `sitemap.xml` at the source root.
|
|
217
|
+
|
|
218
|
+
```js
|
|
219
|
+
const result = await sitemap();
|
|
220
|
+
// result.files === ['src/sitemap.xml']
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## PHP classes reference
|
|
226
|
+
|
|
227
|
+
All classes are autoloaded — no manual `require` needed inside your page files.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
### PREPROS
|
|
232
|
+
|
|
233
|
+
The core engine. Manages the rendering pipeline, tag processing, hooks, and file export.
|
|
234
|
+
|
|
235
|
+
```php
|
|
236
|
+
// Available inside page templates and included files.
|
|
237
|
+
PREPROS::$config // stdClass — full resolved config (prepros section of kirigami.yaml)
|
|
238
|
+
PREPROS::registerTag(string $tag, callable $callback)
|
|
239
|
+
PREPROS::registerHook(string $hook, callable $callback)
|
|
240
|
+
PREPROS::exportFile(string $absolutePath)
|
|
241
|
+
PREPROS::getExportedFiles(): string[]
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
#### `PREPROS::render(string $file)`
|
|
245
|
+
|
|
246
|
+
Internal method called once per source file. Orchestrates the full pipeline:
|
|
247
|
+
|
|
248
|
+
1. Resolves PHPDOC metadata and auto-loads data files.
|
|
249
|
+
2. Fires the `pre_render` hook with the raw source contents.
|
|
250
|
+
3. Includes `before.php`, the page body, and `after.php` into a single string.
|
|
251
|
+
4. Processes all registered custom HTML tags.
|
|
252
|
+
5. Fires the `post_render` hook on the assembled HTML.
|
|
253
|
+
6. Optionally pretty-prints via `HTML::format()` (when `format: true`).
|
|
254
|
+
7. Writes the output `.html` file.
|
|
255
|
+
|
|
256
|
+
#### `PREPROS::sitemap()`
|
|
257
|
+
|
|
258
|
+
Scans the source tree for `_index.php` files and generates a standards-compliant `sitemap.xml` (Sitemaps 0.9).
|
|
259
|
+
|
|
260
|
+
#### `PREPROS::exportFile(string $file)`
|
|
261
|
+
|
|
262
|
+
Marks a file as a build output so it gets surfaced in `PreprosResult.files`. Called automatically by `render()`, `sitemap()`, `CACHE::set()`, and `IMG::save()`. Call it manually if your custom code writes additional files.
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
### MD
|
|
267
|
+
|
|
268
|
+
Markdown-to-HTML converter with a plugin system for custom shortcodes.
|
|
269
|
+
|
|
270
|
+
```php
|
|
271
|
+
$html = MD::toHtml(string $markdown): string;
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Supports the full GitHub Flavored Markdown subset:
|
|
275
|
+
|
|
276
|
+
- ATX headings (`#` through `######`) with auto-generated `id` attributes
|
|
277
|
+
- Ordered and unordered lists, including nested
|
|
278
|
+
- GFM task lists (`- [ ]` / `- [x]`)
|
|
279
|
+
- GFM tables with column alignment
|
|
280
|
+
- GFM alerts (`> [!NOTE]`, `> [!WARNING]`, etc.)
|
|
281
|
+
- Blockquotes (recursive)
|
|
282
|
+
- Fenced code blocks with language class
|
|
283
|
+
- Inline code
|
|
284
|
+
- Bold, italic, bold+italic, strikethrough
|
|
285
|
+
- Links with automatic `target="_blank" rel="noopener noreferrer"` for external URLs
|
|
286
|
+
- Images with `loading="lazy"`
|
|
287
|
+
- Auto-linked bare URLs
|
|
288
|
+
- Horizontal rules
|
|
289
|
+
- Hard line breaks (trailing double space → `<br>`)
|
|
290
|
+
|
|
291
|
+
#### Plugin API
|
|
292
|
+
|
|
293
|
+
Extend Markdown with custom shortcode tags:
|
|
294
|
+
|
|
295
|
+
```php
|
|
296
|
+
// Inline tag {% tagname arg1 "arg with spaces" %}
|
|
297
|
+
// Block tag {% tagname arg1
|
|
298
|
+
// body content
|
|
299
|
+
// %}
|
|
300
|
+
|
|
301
|
+
MD::registerPlugin(string $name, callable $callback): void
|
|
302
|
+
MD::unregisterPlugin(string $name): void
|
|
303
|
+
MD::getRegisteredPlugins(): string[]
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
The callback always receives `(array $args, string $body)`:
|
|
307
|
+
|
|
308
|
+
```php
|
|
309
|
+
MD::registerPlugin('video', function (array $args, string $body): string {
|
|
310
|
+
$src = htmlspecialchars($args[0] ?? '', ENT_QUOTES, 'UTF-8');
|
|
311
|
+
return "<video src=\"{$src}\" controls></video>";
|
|
312
|
+
});
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Then in any Markdown content (including inside `<markdown>` tags):
|
|
316
|
+
|
|
317
|
+
```
|
|
318
|
+
{% video /videos/intro.mp4 %}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
### HTML
|
|
324
|
+
|
|
325
|
+
Pretty-printer for the final HTML output. Used automatically when `format: true` is set in the config.
|
|
326
|
+
|
|
327
|
+
```php
|
|
328
|
+
$formatted = HTML::format(string $html): string;
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Uses PHP 8.4's `Dom\HTMLDocument` (Lexbor engine) to parse the input and re-serialize it with consistent 4-space indentation. Inline elements, `<script>`, and `<style>` blocks are handled correctly — their content is indented but not reformatted. Boolean HTML5 attributes (`muted`, `autoplay`, `noopener`, etc.) are written without a value.
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
### YAML
|
|
336
|
+
|
|
337
|
+
A lightweight, zero-dependency YAML parser. Covers the full subset used in static site projects.
|
|
338
|
+
|
|
339
|
+
```php
|
|
340
|
+
$data = YAML::parse(string $yaml, bool $assoc = false): mixed;
|
|
341
|
+
$data = YAML::parseFile(string $path, bool $assoc = false): mixed;
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Supported features:
|
|
345
|
+
|
|
346
|
+
- Scalars: strings (quoted and unquoted), integers, floats, booleans, null
|
|
347
|
+
- Single and double quoted strings with escape sequences
|
|
348
|
+
- Literal block scalars (`|`, `|-`, `|+`)
|
|
349
|
+
- Folded block scalars (`>`, `>-`, `>+`)
|
|
350
|
+
- Plain scalars spanning multiple lines
|
|
351
|
+
- Nested mappings and sequences
|
|
352
|
+
- Inline collections (`[a, b]` and `{k: v}`)
|
|
353
|
+
- Comments (`#`)
|
|
354
|
+
- Multiple documents separated by `---`
|
|
355
|
+
|
|
356
|
+
By default, YAML mappings are returned as `stdClass` objects. Pass `true` as the second argument to get associative arrays instead.
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
### CACHE
|
|
361
|
+
|
|
362
|
+
Persistent SQLite-backed key-value cache. Survives across incremental builds via `.cache.db` at the project root.
|
|
363
|
+
|
|
364
|
+
```php
|
|
365
|
+
CACHE::get(string $key): mixed
|
|
366
|
+
CACHE::set(string $key, mixed $val, int $ttl = 0): bool
|
|
367
|
+
CACHE::delete(string $key): bool
|
|
368
|
+
CACHE::purge(): bool // removes expired entries
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
The `$ttl` is in seconds. `0` means the entry never expires. Typical use case: caching the result of network fetches in custom hooks or plugins.
|
|
372
|
+
|
|
373
|
+
```php
|
|
374
|
+
$data = CACHE::get('my-remote-data');
|
|
375
|
+
if ($data === null) {
|
|
376
|
+
$data = json_decode(file_get_contents('https://api.example.com/data.json'));
|
|
377
|
+
CACHE::set('my-remote-data', $data, 3600); // cache for 1 hour
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
### IMG
|
|
384
|
+
|
|
385
|
+
Image manipulation helper built on PHP GD. Supports JPEG, PNG, GIF, and WebP.
|
|
386
|
+
|
|
387
|
+
```php
|
|
388
|
+
$img = new IMG(string $file);
|
|
389
|
+
|
|
390
|
+
// Properties
|
|
391
|
+
$img->width // int
|
|
392
|
+
$img->height // int
|
|
393
|
+
|
|
394
|
+
// Methods (chainable)
|
|
395
|
+
$img->resize(int $width, int $height = 0, bool $cover = false): self
|
|
396
|
+
$img->save(string $dest): self
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
`resize()` operates in *contain* mode by default (scales to fit within the target box while preserving aspect ratio). Pass `$cover = true` to crop and fill the exact target dimensions.
|
|
400
|
+
|
|
401
|
+
`save()` infers the output format from the file extension (`.jpg`, `.jpeg`, `.png`, `.gif`, `.webp`). The saved file is automatically registered via `PREPROS::exportFile()`.
|
|
402
|
+
|
|
403
|
+
```php
|
|
404
|
+
(new IMG('/project/src/images/hero.jpg'))
|
|
405
|
+
->resize(1200, 630, true)
|
|
406
|
+
->save('/project/src/images/hero-og.jpg');
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
### FS
|
|
412
|
+
|
|
413
|
+
Filesystem utilities.
|
|
414
|
+
|
|
415
|
+
```php
|
|
416
|
+
FS::dig(string $glob): iterable // recursive glob, yields file paths
|
|
417
|
+
FS::getRelativePath(string $from, string $to): string
|
|
418
|
+
FS::phpFileInfo(string $file): object|false // parse PHPDOC annotations
|
|
419
|
+
FS::rmdir(string $dir, bool $removeSelf = true): bool
|
|
420
|
+
FS::pathJoin(string ...$parts): string // URL-aware path join with .. resolution
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
`FS::dig()` is the workhorse of directory-wide builds — it recursively walks a glob pattern and yields every matching file path.
|
|
424
|
+
|
|
425
|
+
`FS::phpFileInfo()` parses the first PHPDOC block of a PHP file and returns its `@tag value` pairs as a `stdClass`. This is used internally to resolve page metadata and data-file annotations.
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
### STR
|
|
430
|
+
|
|
431
|
+
String utilities used internally by the tag-processing pipeline.
|
|
432
|
+
|
|
433
|
+
```php
|
|
434
|
+
STR::htmlesc(string $str): string
|
|
435
|
+
STR::replaceTags(string $tag, string $html, callable $callback): string
|
|
436
|
+
STR::parseHtmlAttributes(string $attrString): array
|
|
437
|
+
STR::trimIndent(string $str): string
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
`STR::replaceTags()` is the engine behind `PREPROS::registerTag()`. It finds all occurrences of `<tagname ...>...</tagname>` in an HTML string and replaces each with the return value of `$callback($fullMatch, $attrs, $body)`.
|
|
441
|
+
|
|
442
|
+
`STR::trimIndent()` strips the common leading whitespace from a multi-line string — handy when pulling content out of indented `<markdown>` blocks.
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
### OBF
|
|
447
|
+
|
|
448
|
+
Simple reversible obfuscation for values you want to embed in HTML without making them trivially readable (e.g., contact data, API tokens in templates).
|
|
449
|
+
|
|
450
|
+
```php
|
|
451
|
+
$encoded = OBF::encode(mixed $obj): string;
|
|
452
|
+
$decoded = OBF::decode(string $str): mixed;
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Applies JSON encoding → base64 → ROT-13 → gzip. Not cryptographically secure; intended for light obfuscation only.
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
### STD
|
|
460
|
+
|
|
461
|
+
Output helpers used by the PHP runtime to communicate back to Node.js over stdout/stderr.
|
|
462
|
+
|
|
463
|
+
```php
|
|
464
|
+
STD::succeed(array|string $props = []): void // exits 0, writes JSON to stdout
|
|
465
|
+
STD::error(array|string $props = []): void // exits 1, writes JSON to stderr
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
These are internal to the build runner. You generally do not need to call them in page templates.
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
## Plugin system
|
|
473
|
+
|
|
474
|
+
`@kirigami/php-prepros` has two complementary plugin layers: **PREPROS** (HTML-tag level, operates on the assembled page) and **MD** (shortcode level, operates inside Markdown content).
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
### PREPROS tags
|
|
479
|
+
|
|
480
|
+
Register a custom HTML tag that is processed **after** PHP execution, on the fully assembled HTML string:
|
|
481
|
+
|
|
482
|
+
```php
|
|
483
|
+
// In a file listed under prepros.includes in kirigami.yaml, or in before.php:
|
|
484
|
+
|
|
485
|
+
PREPROS::registerTag('gallery', function (string $fullTag, array $attrs, string $body): string {
|
|
486
|
+
$id = $attrs['id'] ?? '';
|
|
487
|
+
$imgs = glob("/project/src/images/gallery/{$id}/*.webp");
|
|
488
|
+
$html = '<div class="gallery">';
|
|
489
|
+
foreach ($imgs as $img) {
|
|
490
|
+
$src = str_replace('/project/src', '', $img);
|
|
491
|
+
$html .= "<img src=\"{$src}\" loading=\"lazy\">";
|
|
492
|
+
}
|
|
493
|
+
return $html . '</div>';
|
|
494
|
+
});
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
Then in any page template:
|
|
498
|
+
|
|
499
|
+
```html
|
|
500
|
+
<gallery id="summer-2025"></gallery>
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
The callback receives:
|
|
504
|
+
|
|
505
|
+
| Parameter | Type | Description |
|
|
506
|
+
|-----------|------|-------------|
|
|
507
|
+
| `$fullTag` | `string` | The complete matched tag string |
|
|
508
|
+
| `$attrs` | `array` | Parsed HTML attributes as an associative array |
|
|
509
|
+
| `$body` | `string` | Inner content between opening and closing tags |
|
|
510
|
+
|
|
511
|
+
The built-in `<markdown>` tag is registered this way (see below).
|
|
512
|
+
|
|
513
|
+
---
|
|
514
|
+
|
|
515
|
+
### PREPROS hooks
|
|
516
|
+
|
|
517
|
+
Hooks let you intercept and transform data at key points in the rendering pipeline:
|
|
518
|
+
|
|
519
|
+
```php
|
|
520
|
+
PREPROS::registerHook(string $hookName, callable $callback): void
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
| Hook | When it fires | `$data` type | Expected return |
|
|
524
|
+
|------|---------------|--------------|-----------------|
|
|
525
|
+
| `page_info` | After PHPDOC parsing, before rendering | `[$filePath, $pageObject]` | `$pageObject` (modified) |
|
|
526
|
+
| `pre_render` | Before PHP execution | Raw file contents as `string` | `string` |
|
|
527
|
+
| `post_render` | After tag processing, before `HTML::format()` | Assembled HTML `string` | `string` |
|
|
528
|
+
|
|
529
|
+
Multiple callbacks can be registered for the same hook — they are executed in registration order, each receiving the return value of the previous one.
|
|
530
|
+
|
|
531
|
+
```php
|
|
532
|
+
// Example: inject a last-modified date into every page
|
|
533
|
+
PREPROS::registerHook('post_render', function (string $html): string {
|
|
534
|
+
$date = date('Y-m-d');
|
|
535
|
+
return str_replace('{{build_date}}', $date, $html);
|
|
536
|
+
});
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
### MD plugins
|
|
542
|
+
|
|
543
|
+
MD plugins add custom shortcode tags inside Markdown content. They work inside `<markdown>` blocks, in `.md` data files, and anywhere `MD::toHtml()` is called.
|
|
544
|
+
|
|
545
|
+
**Inline syntax** (all on one line):
|
|
546
|
+
|
|
547
|
+
```
|
|
548
|
+
{% tagname arg1 "argument with spaces" %}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
**Block syntax** (body on subsequent lines):
|
|
552
|
+
|
|
553
|
+
```
|
|
554
|
+
{% tagname optional-arg
|
|
555
|
+
Line one of the body.
|
|
556
|
+
Line two of the body.
|
|
557
|
+
%}
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
```php
|
|
561
|
+
MD::registerPlugin(string $name, callable $callback): void
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
The callback signature is always `(array $args, string $body): string`. `$args` contains arguments parsed from the opening line; `$body` is the trimmed multi-line body (empty string for inline tags).
|
|
565
|
+
|
|
566
|
+
---
|
|
567
|
+
|
|
568
|
+
### Built-in plugins
|
|
569
|
+
|
|
570
|
+
The following MD plugins are registered out of the box in `md.plugins.php`:
|
|
571
|
+
|
|
572
|
+
#### `{% callout type ["Title"] content %}`
|
|
573
|
+
|
|
574
|
+
Renders a styled callout block. `type` is one of `info`, `success`, `warning`, `danger`.
|
|
575
|
+
|
|
576
|
+
```
|
|
577
|
+
{% callout warning "Heads up" This section is outdated. %}
|
|
578
|
+
|
|
579
|
+
{% callout danger "Critical"
|
|
580
|
+
Line one of a longer warning.
|
|
581
|
+
|
|
582
|
+
Line two after a blank line.
|
|
583
|
+
%}
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
---
|
|
587
|
+
|
|
588
|
+
## Extending the `<markdown>` tag
|
|
589
|
+
|
|
590
|
+
The `<markdown>` tag is registered as a PREPROS tag out of the box. It converts its inner content from Markdown to HTML and strips common leading indentation so you can write cleanly inside your PHP templates:
|
|
591
|
+
|
|
592
|
+
```html
|
|
593
|
+
<section class="about">
|
|
594
|
+
<div>
|
|
595
|
+
<markdown>
|
|
596
|
+
## Who we are
|
|
597
|
+
|
|
598
|
+
We are a **student organization** from Québec.
|
|
599
|
+
|
|
600
|
+
{% youtube dQw4w9WgXcQ %}
|
|
601
|
+
</markdown>
|
|
602
|
+
</div>
|
|
603
|
+
</section>
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
All registered MD plugins are available inside `<markdown>` blocks. You can extend the tag's behaviour by registering additional MD plugins (see above) or by overriding the tag itself:
|
|
607
|
+
|
|
608
|
+
```php
|
|
609
|
+
PREPROS::registerTag('markdown', function (string $tag, array $attrs, string $body): string {
|
|
610
|
+
$body = STR::trimIndent($body);
|
|
611
|
+
$html = MD::toHtml($body);
|
|
612
|
+
// wrap in a container, add a class, etc.
|
|
613
|
+
$class = $attrs['class'] ?? 'prose';
|
|
614
|
+
return "<div class=\"{$class}\">{$html}</div>";
|
|
615
|
+
});
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
---
|
|
619
|
+
|
|
620
|
+
## License
|
|
621
|
+
|
|
622
|
+
MIT © Maxime Larrivée-Roy, 2026
|