@kirigami/php-prepros 1.0.9 → 1.1.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/README.md +300 -59
- package/index.js +1 -1
- package/package.json +4 -3
- package/src/libraries/arr.class.php +29 -0
- package/src/libraries/cache.class.php +5 -4
- package/src/libraries/curl.class.php +106 -0
- package/src/libraries/html.class.php +2 -0
- package/src/libraries/img.class.php +1 -1
- package/src/libraries/prepros.class.php +22 -3
- package/src/libraries/scraper.class.php +320 -0
- package/src/libraries/std.class.php +6 -2
- package/src/libraries/str.class.php +34 -0
- package/src/phpjs/mount.js +28 -0
- package/src/prepros.js +93 -24
- package/src/prepros.php +4 -17
- package/src/runenv.php +17 -0
- package/src/utils/findfile.js +25 -0
- package/src/utils.inc.php +21 -2
package/README.md
CHANGED
|
@@ -17,42 +17,67 @@ Part of the **Kirigami** project ecosystem. Other packages are coming soon.
|
|
|
17
17
|
## Table of contents
|
|
18
18
|
|
|
19
19
|
- [@kirigami/php-prepros](#kirigamiphp-prepros)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
20
|
+
- [Table of contents](#table-of-contents)
|
|
21
|
+
- [What's new in 1.1.0](#whats-new-in-110)
|
|
22
|
+
- [How it works](#how-it-works)
|
|
23
|
+
- [Installation](#installation)
|
|
24
|
+
- [Configuration — `kirigami.yaml`](#configuration--kirigamiyaml)
|
|
25
|
+
- [`kirigami` block](#kirigami-block)
|
|
26
|
+
- [`prepros` block](#prepros-block)
|
|
27
|
+
- [Writing pages](#writing-pages)
|
|
28
|
+
- [PHPDOC header](#phpdoc-header)
|
|
29
|
+
- [Auto-loading data files](#auto-loading-data-files)
|
|
30
|
+
- [`@content` and `@indent`](#content-and-indent)
|
|
31
|
+
- [JavaScript API](#javascript-api)
|
|
32
|
+
- [`render(file?)`](#renderfile)
|
|
33
|
+
- [`sitemap()`](#sitemap)
|
|
34
|
+
- [`runenv(script, paths?, ...args)`](#runenvscript-paths-args)
|
|
35
|
+
- [PHP classes reference](#php-classes-reference)
|
|
36
|
+
- [PREPROS](#prepros)
|
|
37
|
+
- [`PREPROS::render(string $file)`](#preprosrenderstring-file)
|
|
38
|
+
- [`PREPROS::sitemap()`](#preprossitemap)
|
|
39
|
+
- [`PREPROS::mount(string|array $patterns)`](#preprosmountstringarray-patterns)
|
|
40
|
+
- [`PREPROS::exportFile(string $file)`](#preprosexportfilestring-file)
|
|
41
|
+
- [MD](#md)
|
|
42
|
+
- [Plugin API](#plugin-api)
|
|
43
|
+
- [HTML](#html)
|
|
44
|
+
- [YAML](#yaml)
|
|
45
|
+
- [CACHE](#cache)
|
|
46
|
+
- [IMG](#img)
|
|
47
|
+
- [FS](#fs)
|
|
48
|
+
- [STR](#str)
|
|
49
|
+
- [ARR](#arr)
|
|
50
|
+
- [CURL](#curl)
|
|
51
|
+
- [SCRAPER](#scraper)
|
|
52
|
+
- [OBF](#obf)
|
|
53
|
+
- [STD](#std)
|
|
54
|
+
- [Plugin system](#plugin-system)
|
|
55
|
+
- [PREPROS tags](#prepros-tags)
|
|
56
|
+
- [PREPROS hooks](#prepros-hooks)
|
|
57
|
+
- [MD plugins](#md-plugins)
|
|
58
|
+
- [Built-in plugins](#built-in-plugins)
|
|
59
|
+
- [`{% callout type ["Title"] content %}`](#-callout-type-title-content-)
|
|
60
|
+
- [`{% youtube id [width height] %}`](#-youtube-id-width-height-)
|
|
61
|
+
- [`{% codepen id [user height] %}`](#-codepen-id-user-height-)
|
|
62
|
+
- [`{% checklist ["Title"] items %}`](#-checklist-title-items-)
|
|
63
|
+
- [Extending the `<markdown>` tag](#extending-the-markdown-tag)
|
|
64
|
+
- [License](#license)
|
|
53
65
|
|
|
54
66
|
---
|
|
55
67
|
|
|
68
|
+
## What's new in 1.1.0
|
|
69
|
+
|
|
70
|
+
- **`PREPROS::mount()`** — mount extra files into the WASM filesystem on demand, from a glob pattern, at any point during rendering.
|
|
71
|
+
- **`runenv()`** JavaScript export — run an arbitrary PHP script (not a page template) inside the same sandboxed environment, with full access to every `php-prepros` class.
|
|
72
|
+
- **`SCRAPER`** class — fetch a URL and extract `title` / `description` / `image` / `label` from its Open Graph, `<meta>`, and JSON-LD data, with automatic caching.
|
|
73
|
+
- **`CURL`** class — low-level cURL helper used internally by `SCRAPER`, also usable directly (`urlExists()`, `getInfo()`, `getContents()`), with a shared, persisted cookie jar.
|
|
74
|
+
- **`ARR`** class — recursive associative array/object key lookup.
|
|
75
|
+
- **`YAML::loadFile()`** — like `YAML::parseFile()`, but recursively resolves any string value that points to another existing `.yaml`/`.yml`/`.json` file into its parsed content.
|
|
76
|
+
- New `STR` helpers: `STR::is_url()`, `STR::html_entities_decode()`, `STR::shorthash()`, `STR::slug()`.
|
|
77
|
+
- New built-in MD plugins: `{% youtube %}`, `{% codepen %}`, and `{% checklist %}`, alongside the existing `{% callout %}`.
|
|
78
|
+
- `@content` and `@indent` PHPDOC annotations, letting a page skip its own PHP body in favour of pre-rendered content, and control its indentation when nested inside a layout.
|
|
79
|
+
|
|
80
|
+
---
|
|
56
81
|
|
|
57
82
|
## How it works
|
|
58
83
|
|
|
@@ -68,7 +93,7 @@ _index.php ──▶ PHP (wasm) ──▶ processTags() ──▶ HTML::fo
|
|
|
68
93
|
└── PHPDOC annotations resolved (yaml / json / md / url)
|
|
69
94
|
```
|
|
70
95
|
|
|
71
|
-
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.
|
|
96
|
+
Files are mounted into the WebAssembly virtual filesystem on demand. Only `.php`, `.json`, `.yaml`, `.yml`, `.md`, `.db`, `.txt` and any extra extensions listed in `prepros.mountext` are mounted automatically, keeping memory usage low. Anything else can be mounted on demand with [`PREPROS::mount()`](#preprosmountstringarray-patterns).
|
|
72
97
|
|
|
73
98
|
---
|
|
74
99
|
|
|
@@ -88,24 +113,65 @@ Every project **must** have a `kirigami.yaml` at its root. The preprocessor read
|
|
|
88
113
|
|
|
89
114
|
```yaml
|
|
90
115
|
kirigami:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
116
|
+
# ── Required ──────────────────────────────────────────────────────────
|
|
117
|
+
root: src # Source directory containing your _*.php pages.
|
|
118
|
+
|
|
119
|
+
# ── Used internally ──────────────────────────────────────────────────
|
|
120
|
+
baseurl: https://example.com # Used as the base URL when generating sitemap.xml.
|
|
121
|
+
|
|
122
|
+
# ── Arbitrary project data ──────────────────────────────────────────
|
|
123
|
+
# Everything else under `kirigami:` is free-form. The whole block is
|
|
124
|
+
# extracted as PHP variables and made available in every page, in
|
|
125
|
+
# before.php/after.php, and anywhere PREPROS::$config->data is read.
|
|
126
|
+
project: My Website
|
|
127
|
+
author: Jane Doe
|
|
128
|
+
person: John Smith
|
|
129
|
+
jobtitle: Founder
|
|
130
|
+
email: hello@example.com
|
|
131
|
+
facebook: https://www.facebook.com/example
|
|
132
|
+
area: Somewhere, Country
|
|
133
|
+
gtag: G-XXXXXXXXXX
|
|
134
|
+
banner: assets/banner.txt
|
|
135
|
+
description: A short description of the site, useful for <meta name="description">.
|
|
136
|
+
knowsabout:
|
|
137
|
+
- Topic one
|
|
138
|
+
- Topic two
|
|
139
|
+
keywords:
|
|
140
|
+
- keyword one
|
|
141
|
+
- keyword two
|
|
95
142
|
|
|
96
143
|
prepros:
|
|
97
|
-
before:
|
|
98
|
-
after:
|
|
99
|
-
format:
|
|
100
|
-
network: false
|
|
101
|
-
mountext:
|
|
102
|
-
- .svg
|
|
103
|
-
- .
|
|
104
|
-
includes:
|
|
144
|
+
before: _layout/header.php # Included before every page body.
|
|
145
|
+
after: _layout/footer.php # Included after every page body.
|
|
146
|
+
format: true # Pretty-print the HTML output (default: false).
|
|
147
|
+
network: false # Allow HTTP fetches in PHPDOC @tag annotations.
|
|
148
|
+
mountext: # Extra file extensions to auto-mount into the wasm fs,
|
|
149
|
+
- .svg # in addition to the defaults (.php .json .yaml .yml .md .db .txt).
|
|
150
|
+
- .webp
|
|
151
|
+
includes: # PHP files auto-included once, before any page renders.
|
|
105
152
|
- _lib/helpers.php
|
|
106
153
|
```
|
|
107
154
|
|
|
108
|
-
|
|
155
|
+
### `kirigami` block
|
|
156
|
+
|
|
157
|
+
| Key | Required | Description |
|
|
158
|
+
|-----|----------|--------------|
|
|
159
|
+
| `root` | ✅ | Path (relative to the project root) to the directory containing your `_*.php` source pages. Build fails immediately if missing or if the path doesn't exist. |
|
|
160
|
+
| `baseurl` | for `sitemap()` | Root URL used to build absolute `<loc>` entries when generating `sitemap.xml`. |
|
|
161
|
+
| *anything else* | — | Free-form key/value pairs (strings, numbers, booleans, lists, nested maps — anything valid YAML). Every key is extracted as a PHP variable (`$project`, `$author`, `$gtag`, …) and available in page templates, `before.php`, `after.php`, and PHP files listed under `prepros.includes`. Use this to hold your site name, contact info, social links, analytics IDs, SEO keywords, banners, or any project-specific data you want available everywhere. |
|
|
162
|
+
|
|
163
|
+
### `prepros` block
|
|
164
|
+
|
|
165
|
+
| Key | Type | Default | Description |
|
|
166
|
+
|-----|------|---------|--------------|
|
|
167
|
+
| `before` | `string` | — | Path (relative to `kirigami.root`) to a PHP file included **before** every page's body. Typically your `<head>`/layout opening. |
|
|
168
|
+
| `after` | `string` | — | Path (relative to `kirigami.root`) to a PHP file included **after** every page's body. Typically your layout closing. |
|
|
169
|
+
| `format` | `bool` | `false` | Pretty-print the compiled HTML via [`HTML::format()`](#html) before writing it to disk. |
|
|
170
|
+
| `network` | `bool` | `false` | Enables outbound HTTP(S) inside the WASM PHP runtime. Required for PHPDOC `@tag https://…` annotations that fetch remote `.yaml`/`.json`/`.md` data (see [Auto-loading data files](#auto-loading-data-files)). |
|
|
171
|
+
| `mountext` | `string[]` | `[]` | Extra file extensions to mount automatically into the virtual filesystem alongside the built-in `.php`, `.json`, `.yaml`, `.yml`, `.md`, `.db`, `.txt`. Use this for assets your PHP code reads directly (e.g. `.svg`, `.txt`, `.webp`). Files with extensions not in this set are simply skipped during mounting — mount them on demand with [`PREPROS::mount()`](#preprosmountstringarray-patterns) instead. |
|
|
172
|
+
| `includes` | `string[]` | `[]` | PHP files (relative to `kirigami.root`) `include_once`'d once, right after config is loaded — before any page renders. The natural place to `PREPROS::registerTag()`, `PREPROS::registerHook()`, or `MD::registerPlugin()`. |
|
|
173
|
+
|
|
174
|
+
The entire `kirigami` block is extracted into PHP variables and made available in every page template, `before.php`, and `after.php`. `$project`, `$author`, `$gtag`, etc. are available without any further setup.
|
|
109
175
|
|
|
110
176
|
---
|
|
111
177
|
|
|
@@ -148,7 +214,7 @@ Every page starts with a PHP docblock that drives metadata and data loading:
|
|
|
148
214
|
|
|
149
215
|
All annotations are injected as PHP variables (`$name`, `$title`, `$abstract`, …). You can define any custom annotation you need.
|
|
150
216
|
|
|
151
|
-
|
|
217
|
+
Annotations are also available as variables in `before` and `after` PHP included files, so you can write proper metas in the HTML header.
|
|
152
218
|
|
|
153
219
|
### Auto-loading data files
|
|
154
220
|
|
|
@@ -182,12 +248,29 @@ When `network: true` is set in `kirigami.yaml`, annotation values that start wit
|
|
|
182
248
|
*/
|
|
183
249
|
```
|
|
184
250
|
|
|
251
|
+
### `@content` and `@indent`
|
|
252
|
+
|
|
253
|
+
Two special annotation names change how a page's body is assembled:
|
|
254
|
+
|
|
255
|
+
- **`@content`** — if a `content` variable already resolves to a non-empty value (typically because it's a `.md`/`.yaml`/`.json` annotation that auto-loaded into HTML/data, see above), it is used **as-is** as the page body, and the PHP file itself is **not executed** for its output. This is handy for pages that are pure data/markdown wrapped by a shared layout.
|
|
256
|
+
- **`@indent`** — when set to a number, every line of the rendered body is prefixed with that many spaces before being wrapped by `before.php`/`after.php`. Useful for keeping generated HTML readable when a page is nested inside indented layout markup.
|
|
257
|
+
|
|
258
|
+
```php
|
|
259
|
+
<?php
|
|
260
|
+
/**
|
|
261
|
+
* @name changelog
|
|
262
|
+
* @title Changelog
|
|
263
|
+
* @content _changelog.md
|
|
264
|
+
* @indent 4
|
|
265
|
+
*/
|
|
266
|
+
```
|
|
267
|
+
|
|
185
268
|
---
|
|
186
269
|
|
|
187
270
|
## JavaScript API
|
|
188
271
|
|
|
189
272
|
```js
|
|
190
|
-
import { render, sitemap } from '@kirigami/php-prepros';
|
|
273
|
+
import { render, sitemap, runenv } from '@kirigami/php-prepros';
|
|
191
274
|
```
|
|
192
275
|
|
|
193
276
|
### `render(file?)`
|
|
@@ -204,7 +287,7 @@ const result = await render('.');
|
|
|
204
287
|
// Compile everything (uses kirigami.root from config)
|
|
205
288
|
const result = await render();
|
|
206
289
|
```
|
|
207
|
-
>
|
|
290
|
+
> Paths used by `render()` are all relative to the `kirigami.root` configuration.
|
|
208
291
|
|
|
209
292
|
|
|
210
293
|
**Returns** `Promise<PreprosResult>`:
|
|
@@ -226,6 +309,27 @@ const result = await sitemap();
|
|
|
226
309
|
// result.files === ['src/sitemap.xml']
|
|
227
310
|
```
|
|
228
311
|
|
|
312
|
+
### `runenv(script, paths?, ...args)`
|
|
313
|
+
|
|
314
|
+
Run an arbitrary PHP script — not a page template — inside the very same sandboxed WASM environment used for `render()`, with the full `php-prepros` class library autoloaded and `kirigami.yaml`'s `kirigami` block available as `PREPROS::$config->data`. Useful for one-off maintenance scripts, data migrations, or CLI-style tooling that needs `CACHE`, `SCRAPER`, `IMG`, etc. without going through the page-rendering pipeline.
|
|
315
|
+
|
|
316
|
+
```js
|
|
317
|
+
// Run a standalone PHP script
|
|
318
|
+
const result = await runenv('scripts/purge-cache.php');
|
|
319
|
+
|
|
320
|
+
// Also mount extra local paths/files into the sandbox before running
|
|
321
|
+
const result = await runenv('scripts/build-og-images.php', ['assets/photos']);
|
|
322
|
+
|
|
323
|
+
// Extra arguments are appended and available as $argv[2], $argv[3], … in the script
|
|
324
|
+
const result = await runenv('scripts/import.php', [], '--force');
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
- `script` — path to a PHP file **inside the project**, executed with `require_once`.
|
|
328
|
+
- `paths` — optional array of extra local paths (files or directories) to mount into the sandbox before the script runs.
|
|
329
|
+
- `...args` — extra string arguments appended to the script's `$argv`.
|
|
330
|
+
|
|
331
|
+
**Returns** `Promise<PreprosResult>`, following the same shape as `render()`. Inside the script, call `PREPROS::exportFile()` for any file you want listed in `result.files`.
|
|
332
|
+
|
|
229
333
|
---
|
|
230
334
|
|
|
231
335
|
## PHP classes reference
|
|
@@ -236,13 +340,14 @@ All classes are autoloaded — no manual `require` needed inside your page files
|
|
|
236
340
|
|
|
237
341
|
### PREPROS
|
|
238
342
|
|
|
239
|
-
The core engine. Manages the rendering pipeline, tag processing, hooks, and file export.
|
|
343
|
+
The core engine. Manages the rendering pipeline, tag processing, hooks, mounting, and file export.
|
|
240
344
|
|
|
241
345
|
```php
|
|
242
346
|
// Available inside page templates and included files.
|
|
243
347
|
PREPROS::$config // stdClass — full resolved config (prepros section of kirigami.yaml)
|
|
244
348
|
PREPROS::registerTag(string $tag, callable $callback)
|
|
245
349
|
PREPROS::registerHook(string $hook, callable $callback)
|
|
350
|
+
PREPROS::mount(string|array $patterns)
|
|
246
351
|
PREPROS::exportFile(string $absolutePath)
|
|
247
352
|
PREPROS::getExportedFiles(): string[]
|
|
248
353
|
```
|
|
@@ -253,7 +358,7 @@ Internal method called once per source file. Orchestrates the full pipeline:
|
|
|
253
358
|
|
|
254
359
|
1. Resolves PHPDOC metadata and auto-loads data files.
|
|
255
360
|
2. Fires the `pre_render` hook with the raw source contents.
|
|
256
|
-
3. Includes `before.php`, the page body, and `after.php` into a single string.
|
|
361
|
+
3. Includes `before.php`, the page body (or `@content`, see [above](#content-and-indent)), and `after.php` into a single string.
|
|
257
362
|
4. Processes all registered custom HTML tags.
|
|
258
363
|
5. Fires the `post_render` hook on the assembled HTML.
|
|
259
364
|
6. Optionally pretty-prints via `HTML::format()` (when `format: true`).
|
|
@@ -261,11 +366,25 @@ Internal method called once per source file. Orchestrates the full pipeline:
|
|
|
261
366
|
|
|
262
367
|
#### `PREPROS::sitemap()`
|
|
263
368
|
|
|
264
|
-
Scans the source tree for `_index.php` files and generates a standards-compliant `sitemap.xml` (Sitemaps 0.9).
|
|
369
|
+
Scans the source tree for `_index.php` files and generates a standards-compliant `sitemap.xml` (Sitemaps 0.9), using `kirigami.baseurl` as the root URL.
|
|
370
|
+
|
|
371
|
+
#### `PREPROS::mount(string|array $patterns)`
|
|
372
|
+
|
|
373
|
+
Mounts additional local project files into the WASM virtual filesystem, on demand, from one or more glob patterns evaluated against the project root (via `picomatch`). Unlike the automatic mounting done for `kirigami.root` (limited to `.php`, `.json`, `.yaml`, `.yml`, `.md`, `.db`, `.txt`, and `prepros.mountext`), `mount()` copies **any** matching file, regardless of extension.
|
|
374
|
+
|
|
375
|
+
```php
|
|
376
|
+
// Mount every .webp under assets/, wherever the page needs them
|
|
377
|
+
PREPROS::mount('assets/**/*.webp');
|
|
378
|
+
|
|
379
|
+
// Multiple patterns at once
|
|
380
|
+
PREPROS::mount(['data/**/*.csv', 'vendor/fonts/*.woff2']);
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
Returns an array of the virtual paths (under `/project/...`) that were mounted, or `false` on failure.
|
|
265
384
|
|
|
266
385
|
#### `PREPROS::exportFile(string $file)`
|
|
267
386
|
|
|
268
|
-
Marks a file as a build output so it gets surfaced in `PreprosResult.files`. Called automatically by `render()`, `sitemap()`, `CACHE::set()`, and `
|
|
387
|
+
Marks a file as a build output so it gets surfaced in `PreprosResult.files`. Called automatically by `render()`, `sitemap()`, `CACHE::set()`, and `CURL`. Call it manually if your custom code writes additional files.
|
|
269
388
|
|
|
270
389
|
---
|
|
271
390
|
|
|
@@ -345,6 +464,7 @@ A lightweight, zero-dependency YAML parser. Covers the full subset used in stati
|
|
|
345
464
|
```php
|
|
346
465
|
$data = YAML::parse(string $yaml, bool $assoc = false): mixed;
|
|
347
466
|
$data = YAML::parseFile(string $path, bool $assoc = false): mixed;
|
|
467
|
+
$data = YAML::loadFile(string $path, bool $assoc = false): mixed;
|
|
348
468
|
```
|
|
349
469
|
|
|
350
470
|
Supported features:
|
|
@@ -361,6 +481,21 @@ Supported features:
|
|
|
361
481
|
|
|
362
482
|
By default, YAML mappings are returned as `stdClass` objects. Pass `true` as the second argument to get associative arrays instead.
|
|
363
483
|
|
|
484
|
+
`YAML::loadFile()` behaves like `YAML::parseFile()`, then walks the result recursively: any string value ending in `.yaml`, `.yml`, or `.json` that resolves to an existing file (relative to *its own* file's directory) is replaced by that file's parsed content, and so on, recursively. Values that don't match an existing file are left untouched. Circular references (`A → B → A`) throw a `RuntimeException`.
|
|
485
|
+
|
|
486
|
+
```yaml
|
|
487
|
+
# team.yaml
|
|
488
|
+
lead: people/jane.yaml # resolved and inlined automatically
|
|
489
|
+
members:
|
|
490
|
+
- people/jane.yaml
|
|
491
|
+
- people/john.yaml
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
```php
|
|
495
|
+
$team = YAML::loadFile('/project/data/team.yaml');
|
|
496
|
+
// $team->lead is now the fully parsed content of people/jane.yaml, not a string
|
|
497
|
+
```
|
|
498
|
+
|
|
364
499
|
---
|
|
365
500
|
|
|
366
501
|
### CACHE
|
|
@@ -374,7 +509,7 @@ CACHE::delete(string $key): bool
|
|
|
374
509
|
CACHE::purge(): bool // removes expired entries
|
|
375
510
|
```
|
|
376
511
|
|
|
377
|
-
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.
|
|
512
|
+
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 — it is what powers [`SCRAPER`](#scraper) and `CURL`'s cookie persistence internally.
|
|
378
513
|
|
|
379
514
|
```php
|
|
380
515
|
$data = CACHE::get('my-remote-data');
|
|
@@ -404,7 +539,7 @@ $img->save(string $dest): self
|
|
|
404
539
|
|
|
405
540
|
`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.
|
|
406
541
|
|
|
407
|
-
`save()` infers the output format from the file extension (`.jpg`, `.jpeg`, `.png`, `.gif`, `.webp`).
|
|
542
|
+
`save()` infers the output format from the file extension (`.jpg`, `.jpeg`, `.png`, `.gif`, `.webp`).
|
|
408
543
|
|
|
409
544
|
```php
|
|
410
545
|
(new IMG('/project/src/images/hero.jpg'))
|
|
@@ -434,19 +569,95 @@ FS::pathJoin(string ...$parts): string // URL-aware path join with .. resoluti
|
|
|
434
569
|
|
|
435
570
|
### STR
|
|
436
571
|
|
|
437
|
-
String utilities used internally by the tag-processing pipeline.
|
|
572
|
+
String utilities used internally by the tag-processing pipeline, and available for your own templates and plugins.
|
|
438
573
|
|
|
439
574
|
```php
|
|
440
575
|
STR::htmlesc(string $str): string
|
|
441
576
|
STR::replaceTags(string $tag, string $html, callable $callback): string
|
|
442
577
|
STR::parseHtmlAttributes(string $attrString): array
|
|
443
578
|
STR::trimIndent(string $str): string
|
|
579
|
+
STR::is_url(string $str): bool
|
|
580
|
+
STR::html_entities_decode(string $str): string
|
|
581
|
+
STR::shorthash(string $str): string
|
|
582
|
+
STR::slug(string $str): string
|
|
444
583
|
```
|
|
445
584
|
|
|
446
585
|
`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)`.
|
|
447
586
|
|
|
448
587
|
`STR::trimIndent()` strips the common leading whitespace from a multi-line string — handy when pulling content out of indented `<markdown>` blocks.
|
|
449
588
|
|
|
589
|
+
`STR::is_url()` checks whether a string parses as a URL with a recognized scheme (`http`, `https`, `ftp`, `ftps`, `ssh`, `ssl`, `sftp`, `itunes`).
|
|
590
|
+
|
|
591
|
+
`STR::html_entities_decode()` trims a string and decodes its HTML entities — handy when normalizing text scraped from a third-party page.
|
|
592
|
+
|
|
593
|
+
`STR::shorthash()` returns the first 12 characters of a string's SHA-256 hash — used internally as a stable, filename-safe cache key (see `SCRAPER`).
|
|
594
|
+
|
|
595
|
+
`STR::slug()` transliterates a string to ASCII, lowercases it, and strips anything that isn't `[a-z0-9]` — a compact identifier rather than a hyphenated slug.
|
|
596
|
+
|
|
597
|
+
---
|
|
598
|
+
|
|
599
|
+
### ARR
|
|
600
|
+
|
|
601
|
+
Recursive lookup helper for nested arrays and objects.
|
|
602
|
+
|
|
603
|
+
```php
|
|
604
|
+
ARR::find_key(mixed $data, string $key): mixed
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
Walks an array or object (including mixed nested `stdClass`/array structures, as produced by `YAML::parse()` or `json_decode()`) depth-first and returns the value of the **first** matching key found, at any depth, or `null` if none matches.
|
|
608
|
+
|
|
609
|
+
```php
|
|
610
|
+
$config = YAML::parseFile('team.yaml');
|
|
611
|
+
$email = ARR::find_key($config, 'email'); // finds `email` however deep it's nested
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
---
|
|
615
|
+
|
|
616
|
+
### CURL
|
|
617
|
+
|
|
618
|
+
Low-level HTTP client built on PHP's cURL extension, used internally by `SCRAPER`. Ships with a realistic browser `User-Agent`/header set and a cookie jar persisted at `.cookie.txt` (auto-registered via `PREPROS::exportFile()`).
|
|
619
|
+
|
|
620
|
+
```php
|
|
621
|
+
CURL::urlExists(string $url, ?string $mimereg = null): bool
|
|
622
|
+
CURL::getInfo(string $url): array|false // HEAD request, returns curl_getinfo()
|
|
623
|
+
CURL::getContents(string $file, ?string $dest = null, ?callable $clb = null): string|bool
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
`CURL::urlExists()` issues a `HEAD` request and returns `true` for any `2xx`/`3xx` response.
|
|
627
|
+
|
|
628
|
+
`CURL::getContents()` downloads a URL. Without `$dest`, it returns the body as a string; with `$dest`, it streams the download to that file path and returns a boolean. Pass `$clb` to receive download progress as a float between `0` and `1`.
|
|
629
|
+
|
|
630
|
+
```php
|
|
631
|
+
CURL::getContents('https://example.com/report.pdf', '/project/src/downloads/report.pdf', function (float $progress) {
|
|
632
|
+
error_log(sprintf('%.0f%%', $progress * 100));
|
|
633
|
+
});
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
---
|
|
637
|
+
|
|
638
|
+
### SCRAPER
|
|
639
|
+
|
|
640
|
+
Fetches a URL and extracts page metadata (`title`, `description`, `image`, `label`) from its JSON-LD (`schema.org`), Open Graph, and standard `<meta>` tags — the kind of data you'd want for a rich link preview. Results are cached indefinitely via `CACHE`, keyed on the URL.
|
|
641
|
+
|
|
642
|
+
```php
|
|
643
|
+
$metas = SCRAPER::get(string $url): object|false;
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
```php
|
|
647
|
+
$metas = SCRAPER::get('https://example.com/blog/some-article');
|
|
648
|
+
if ($metas) {
|
|
649
|
+
echo $metas->title; // string
|
|
650
|
+
echo $metas->description; // string
|
|
651
|
+
echo $metas->image; // string (absolute URL, may be empty)
|
|
652
|
+
echo $metas->label; // string — site/publisher name, may be empty
|
|
653
|
+
echo $metas->url; // string — the URL that was scraped
|
|
654
|
+
}
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
Returns `false` if the page can't be reached, can't be parsed, or has no discoverable title. Throws an `Exception` on invalid URLs. Uses `CURL::getContents()` under the hood, so it benefits from the same shared cookie jar and browser-like headers.
|
|
658
|
+
|
|
659
|
+
A free `url_exists(string $url, ?string $mimereg = null): bool` function is also available globally (not namespaced under a class), cached the same way as `SCRAPER::get()`.
|
|
660
|
+
|
|
450
661
|
---
|
|
451
662
|
|
|
452
663
|
### OBF
|
|
@@ -471,7 +682,7 @@ STD::succeed(array|string $props = []): void // exits 0, writes JSON to stdout
|
|
|
471
682
|
STD::error(array|string $props = []): void // exits 1, writes JSON to stderr
|
|
472
683
|
```
|
|
473
684
|
|
|
474
|
-
These are internal to the build runner. You generally do not need to call them in page templates.
|
|
685
|
+
These are internal to the build runner (`render()`, `sitemap()`, and `runenv()` all rely on them). You generally do not need to call them in page templates, but they are available if a script run via `runenv()` needs to terminate early with a custom result.
|
|
475
686
|
|
|
476
687
|
---
|
|
477
688
|
|
|
@@ -528,7 +739,7 @@ PREPROS::registerHook(string $hookName, callable $callback): void
|
|
|
528
739
|
|
|
529
740
|
| Hook | When it fires | `$data` type | Expected return |
|
|
530
741
|
|------|---------------|--------------|-----------------|
|
|
531
|
-
| `page_info` | After PHPDOC parsing, before rendering | `[$filePath, $pageObject]` | `$pageObject` (modified) |
|
|
742
|
+
| `page_info` | After PHPDOC parsing, before rendering (auto-loads `.yaml`/`.json`/`.md` annotations) | `[$filePath, $pageObject]` | `$pageObject` (modified) |
|
|
532
743
|
| `pre_render` | Before PHP execution | Raw file contents as `string` | `string` |
|
|
533
744
|
| `post_render` | After tag processing, before `HTML::format()` | Assembled HTML `string` | `string` |
|
|
534
745
|
|
|
@@ -589,6 +800,36 @@ Line two after a blank line.
|
|
|
589
800
|
%}
|
|
590
801
|
```
|
|
591
802
|
|
|
803
|
+
#### `{% youtube id [width height] %}`
|
|
804
|
+
|
|
805
|
+
Embeds a responsive YouTube player via `<iframe>`. `width`/`height` default to `560`/`315`.
|
|
806
|
+
|
|
807
|
+
```
|
|
808
|
+
{% youtube dQw4w9WgXcQ %}
|
|
809
|
+
{% youtube dQw4w9WgXcQ 800 450 %}
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
#### `{% codepen id [user height] %}`
|
|
813
|
+
|
|
814
|
+
Embeds a CodePen result via `<iframe>`. `user` defaults to `anonymous`, `height` defaults to `400`.
|
|
815
|
+
|
|
816
|
+
```
|
|
817
|
+
{% codepen abcXYZ %}
|
|
818
|
+
{% codepen abcXYZ jsmith 500 %}
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
#### `{% checklist ["Title"] items %}`
|
|
822
|
+
|
|
823
|
+
Renders a block-syntax list of checkbox items, one per line, with an optional title.
|
|
824
|
+
|
|
825
|
+
```
|
|
826
|
+
{% checklist "Today"
|
|
827
|
+
Do the dishes
|
|
828
|
+
Walk the dog
|
|
829
|
+
Read a book
|
|
830
|
+
%}
|
|
831
|
+
```
|
|
832
|
+
|
|
592
833
|
---
|
|
593
834
|
|
|
594
835
|
## Extending the `<markdown>` tag
|
package/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { render, sitemap } from "./src/prepros.js";
|
|
1
|
+
export { render, sitemap, runenv } from "./src/prepros.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kirigami/php-prepros",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "PHP preprocessor for the Kirigami static site generator. Compile PHP page templates to clean, deployable HTML — with zero server dependency.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"kirigami",
|
|
@@ -44,8 +44,9 @@
|
|
|
44
44
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@kirigami/php-wasm": "8.5.10-
|
|
48
|
-
"@kirigami/struct-walker": "1.0.3"
|
|
47
|
+
"@kirigami/php-wasm": "8.5.10-3",
|
|
48
|
+
"@kirigami/struct-walker": "1.0.3",
|
|
49
|
+
"picomatch": "^4.0.7"
|
|
49
50
|
},
|
|
50
51
|
"repository": {
|
|
51
52
|
"type": "git",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ARR
|
|
5
|
+
{
|
|
6
|
+
public static function find_key(mixed $data, string $key)
|
|
7
|
+
{
|
|
8
|
+
if (is_object($data)) {
|
|
9
|
+
$data = (array) $data;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (is_array($data)) {
|
|
13
|
+
foreach ($data as $k => $value) {
|
|
14
|
+
if ($k === $key) {
|
|
15
|
+
return $value;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (is_array($value) || is_object($value)) {
|
|
19
|
+
$found = ARR::find_key($value, $key);
|
|
20
|
+
if ($found !== null) {
|
|
21
|
+
return $found;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -13,10 +13,7 @@ class CACHE
|
|
|
13
13
|
static $db = null;
|
|
14
14
|
if($db !== null) return $db;
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
elseif(!empty(PREPROS::$config->root)) $root = rtrim(PREPROS::$config->root, '/') . '/';
|
|
18
|
-
else throw new Exception("Can't find project root.");
|
|
19
|
-
|
|
16
|
+
|
|
20
17
|
if(!is_file(($dbfile = self::getPath()))) $create = true;
|
|
21
18
|
if(!$db = new SQLite3($dbfile)) throw new Exception("Can't open .cache.db.");
|
|
22
19
|
|
|
@@ -36,6 +33,10 @@ class CACHE
|
|
|
36
33
|
|
|
37
34
|
|
|
38
35
|
private static function getPath() {
|
|
36
|
+
// if(!empty($_SERVER['NODE_PROJECT'])) $root = $_SERVER['NODE_PROJECT'] . '/';
|
|
37
|
+
// elseif(!empty(PREPROS::$config->root)) $root = rtrim(PREPROS::$config->root, '/') . '/';
|
|
38
|
+
// else throw new Exception("Can't find project root.");
|
|
39
|
+
// return $root . '.cache.db';
|
|
39
40
|
return '/project/.cache.db';
|
|
40
41
|
}
|
|
41
42
|
|