@kirigami/php-prepros 1.1.0 → 1.6.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 +1593 -869
- package/index.d.ts +145 -2
- package/index.js +1 -1
- package/package.json +4 -4
- package/src/imagebatch.php +67 -0
- package/src/libraries/aliases.inc.php +836 -0
- package/src/libraries/curl.class.php +20 -3
- package/src/libraries/fs.class.php +161 -6
- package/src/libraries/html.class.php +92 -22
- package/src/libraries/img.class.php +372 -17
- package/src/libraries/ld.class.php +804 -0
- package/src/libraries/md.class.php +502 -173
- package/src/libraries/md.plugins.php +25 -19
- package/src/libraries/normalizer.class.php +310 -0
- package/src/libraries/prepros.class.php +149 -10
- package/src/libraries/prepros.plugins.php +56 -6
- package/src/libraries/schema.class.php +550 -0
- package/src/libraries/scraper.class.php +64 -59
- package/src/libraries/std.class.php +3 -0
- package/src/libraries/str.class.php +50 -16
- package/src/libraries/yaml.class.php +66 -66
- package/src/phpjs/fstat.js +9 -0
- package/src/prepros.js +126 -47
- package/src/prepros.php +9 -2
- package/src/runenv.php +5 -2
- package/src/utils/getfilestats.js +30 -0
- package/src/utils/isbinary.js +14 -14
- package/src/utils.inc.php +20 -9
|
@@ -0,0 +1,836 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Procedural shortcuts for the class library in this folder.
|
|
5
|
+
*
|
|
6
|
+
* Every helper is a thin wrapper over a static method, named
|
|
7
|
+
* `<lowercase class>_<snake_case method>()`:
|
|
8
|
+
*
|
|
9
|
+
* CURL::getContents(...) => curl_get_contents(...)
|
|
10
|
+
* IMG::asset(...) => img_asset(...)
|
|
11
|
+
* MD::toHtml(...) => md_to_html(...)
|
|
12
|
+
*
|
|
13
|
+
* Handy inside page templates and `kiri run` scripts, where the terse
|
|
14
|
+
* function form reads better than the fully-qualified static call. The
|
|
15
|
+
* classes stay the canonical API; these only forward their arguments.
|
|
16
|
+
*
|
|
17
|
+
* Loaded from utils.inc.php, right after the autoloader is registered.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
// ===========================================================================
|
|
22
|
+
// PREPROS — the engine
|
|
23
|
+
// ===========================================================================
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Compiles a page template (`_name.php`) to its `.html` sibling: runs the
|
|
27
|
+
* `before`/`after` includes, processes registered tags, fires the
|
|
28
|
+
* `pre_render` / `post_render` hooks, applies `HTML::format()` when
|
|
29
|
+
* `format` is on, then marks the result as a build output.
|
|
30
|
+
*
|
|
31
|
+
* @param string $file Absolute (or cwd-relative) path to the template.
|
|
32
|
+
* @return string|false Absolute path of the generated HTML, or false
|
|
33
|
+
* if `$file` could not be resolved.
|
|
34
|
+
* @see PREPROS::render()
|
|
35
|
+
*/
|
|
36
|
+
function prepros_render(string $file) { return PREPROS::render($file); }
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Generates `sitemap.xml` and `robots.txt` at the project root from the
|
|
40
|
+
* tree of `_index.php` pages, and marks both as build outputs.
|
|
41
|
+
*
|
|
42
|
+
* @return string|false Absolute path of the generated `sitemap.xml`.
|
|
43
|
+
* @see PREPROS::sitemap()
|
|
44
|
+
*/
|
|
45
|
+
function prepros_sitemap() { return PREPROS::sitemap(); }
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Mounts extra local files (any extension) into the WASM filesystem so PHP
|
|
49
|
+
* can read them during the build.
|
|
50
|
+
*
|
|
51
|
+
* @param string|string[] $patterns One or more glob patterns, relative to cwd.
|
|
52
|
+
* @return string[]|false Virtual paths of the mounted files, or
|
|
53
|
+
* false on failure.
|
|
54
|
+
* @see PREPROS::mount()
|
|
55
|
+
*/
|
|
56
|
+
function prepros_mount(string|array $patterns) { return PREPROS::mount($patterns); }
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Stats a file inside the WASM filesystem.
|
|
60
|
+
*
|
|
61
|
+
* @param string $path Virtual path to inspect.
|
|
62
|
+
* @return object|false `stdClass` with `->exists`, `->modifiedAt`, …,
|
|
63
|
+
* or false when the file does not exist.
|
|
64
|
+
* @see PREPROS::fstat()
|
|
65
|
+
*/
|
|
66
|
+
function prepros_fstat(string $path) { return PREPROS::fstat($path); }
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Marks one or more files as build outputs (they show up in the result and
|
|
70
|
+
* get copied back to the host).
|
|
71
|
+
*
|
|
72
|
+
* @param string|string[] $file Absolute path(s) to the generated file(s).
|
|
73
|
+
* @return void
|
|
74
|
+
* @see PREPROS::exportFile()
|
|
75
|
+
*/
|
|
76
|
+
function prepros_export_file(string|array $file): void { PREPROS::exportFile($file); }
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Returns the list of files marked as build outputs so far (de-duplicated).
|
|
80
|
+
*
|
|
81
|
+
* @return string[] Absolute paths.
|
|
82
|
+
* @see PREPROS::getExportedFiles()
|
|
83
|
+
*/
|
|
84
|
+
function prepros_get_exported_files(): array { return PREPROS::getExportedFiles(); }
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Path of the page template currently being rendered.
|
|
88
|
+
*
|
|
89
|
+
* @return string|false The template's path, or false outside of a render.
|
|
90
|
+
* @see PREPROS::backtraceFile()
|
|
91
|
+
*/
|
|
92
|
+
function prepros_backtrace_file() { return PREPROS::backtraceFile(); }
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Registers a custom HTML tag handler, invoked during `post_render`.
|
|
96
|
+
*
|
|
97
|
+
* The callback receives `($fullTag, array $attrs, string $body)` and returns
|
|
98
|
+
* the replacement string, e.g.:
|
|
99
|
+
*
|
|
100
|
+
* prepros_register_tag('gallery', fn($tag, $attrs, $body) => '<div class="gallery">…</div>');
|
|
101
|
+
*
|
|
102
|
+
* @param string $tag Tag name, without angle brackets (`gallery`).
|
|
103
|
+
* @param callable $clb `function(string $fullTag, array $attrs, string $body): string`
|
|
104
|
+
* @return void
|
|
105
|
+
* @see PREPROS::registerTag()
|
|
106
|
+
*/
|
|
107
|
+
function prepros_register_tag(string $tag, callable $clb) { return PREPROS::registerTag($tag, $clb); }
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Registers a build hook.
|
|
111
|
+
*
|
|
112
|
+
* Known hooks: `boot` (`stdClass $config`, once per process after bootstrap,
|
|
113
|
+
* before any render — return value ignored), `page_info` (fires with
|
|
114
|
+
* `[$file, stdClass $info]`; each callback returns the `$info` object it wants
|
|
115
|
+
* to pass on, so a callback registered later receives that bare object — accept
|
|
116
|
+
* both shapes, e.g. `$page = is_array($p) ? $p[1] : $p;`), `pre_render` (raw
|
|
117
|
+
* template source), `pre_before` / `pre_after`
|
|
118
|
+
* (the `before` / `after` config path, just before that include — echo here to
|
|
119
|
+
* prepend output, return value ignored), `post_before` / `post_after` (the
|
|
120
|
+
* captured header / footer string, right after the include), `post_render`
|
|
121
|
+
* (assembled HTML). The callback receives the hook payload and must return it
|
|
122
|
+
* (possibly modified).
|
|
123
|
+
*
|
|
124
|
+
* @param string $hook Hook name.
|
|
125
|
+
* @param callable $clb `function(mixed $payload): mixed`
|
|
126
|
+
* @return void
|
|
127
|
+
* @see PREPROS::registerHook()
|
|
128
|
+
*/
|
|
129
|
+
function prepros_register_hook(string $hook, callable $clb) { return PREPROS::registerHook($hook, $clb); }
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Fires a hook and returns the payload after every registered callback has
|
|
133
|
+
* piped it through. Works for the built-in hooks and for any custom hook name
|
|
134
|
+
* a plugin defines.
|
|
135
|
+
*
|
|
136
|
+
* @param string $hook Hook name.
|
|
137
|
+
* @param mixed $data Initial payload.
|
|
138
|
+
* @return mixed The payload after the last callback.
|
|
139
|
+
* @see PREPROS::runHook()
|
|
140
|
+
*/
|
|
141
|
+
function prepros_run_hook(string $hook, mixed $data = null): mixed { return PREPROS::runHook($hook, $data); }
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Alias of {@see prepros_register_tag()} (historical unprefixed spelling).
|
|
145
|
+
*
|
|
146
|
+
* @param string $tag
|
|
147
|
+
* @param callable $clb `function(string $fullTag, array $attrs, string $body): string`
|
|
148
|
+
* @return void
|
|
149
|
+
*/
|
|
150
|
+
function register_tag(string $tag, callable $clb) { return PREPROS::registerTag($tag, $clb); }
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Alias of {@see prepros_register_hook()} (historical unprefixed spelling).
|
|
154
|
+
*
|
|
155
|
+
* @param string $hook
|
|
156
|
+
* @param callable $clb `function(mixed $payload): mixed`
|
|
157
|
+
* @return void
|
|
158
|
+
*/
|
|
159
|
+
function register_hook(string $hook, callable $clb) { return PREPROS::registerHook($hook, $clb); }
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
// ===========================================================================
|
|
163
|
+
// MD — Markdown -> HTML
|
|
164
|
+
// ===========================================================================
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Converts Markdown to HTML (GitHub-flavored: tables, task lists, GFM
|
|
168
|
+
* alerts, footnotes, reference links, `:emoji:` shortcodes, a safe raw-HTML
|
|
169
|
+
* subset, and `{% plugin %}` tags).
|
|
170
|
+
*
|
|
171
|
+
* @param string $markdown Markdown source.
|
|
172
|
+
* @return string HTML fragment.
|
|
173
|
+
* @see MD::toHtml()
|
|
174
|
+
*/
|
|
175
|
+
function md_to_html(string $markdown): string { return MD::toHtml($markdown); }
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Registers a Markdown plugin, usable as `{% name arg "arg 2" %}` (inline)
|
|
179
|
+
* or as a multi-line `{% name … %}` block.
|
|
180
|
+
*
|
|
181
|
+
* md_register_plugin('video', fn(array $args, string $body) => '<iframe …></iframe>');
|
|
182
|
+
*
|
|
183
|
+
* @param string $name Tag name (case-insensitive).
|
|
184
|
+
* @param callable $clb `function(array $args, string $body): string`
|
|
185
|
+
* @return void
|
|
186
|
+
* @see MD::registerPlugin()
|
|
187
|
+
*/
|
|
188
|
+
function md_register_plugin(string $name, callable $clb): void { MD::registerPlugin($name, $clb); }
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Removes a previously registered Markdown plugin.
|
|
192
|
+
*
|
|
193
|
+
* @param string $name Tag name.
|
|
194
|
+
* @return void
|
|
195
|
+
* @see MD::unregisterPlugin()
|
|
196
|
+
*/
|
|
197
|
+
function md_unregister_plugin(string $name): void { MD::unregisterPlugin($name); }
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Names of all currently registered Markdown plugins.
|
|
201
|
+
*
|
|
202
|
+
* @return string[]
|
|
203
|
+
* @see MD::getRegisteredPlugins()
|
|
204
|
+
*/
|
|
205
|
+
function md_get_registered_plugins(): array { return MD::getRegisteredPlugins(); }
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Registers (or overrides) a `:shortcode:` → character emoji mapping.
|
|
209
|
+
*
|
|
210
|
+
* @param string $shortcode Shortcode, with or without the surrounding colons.
|
|
211
|
+
* @param string $char Replacement character(s).
|
|
212
|
+
* @return void
|
|
213
|
+
* @see MD::registerEmoji()
|
|
214
|
+
*/
|
|
215
|
+
function md_register_emoji(string $shortcode, string $char): void { MD::registerEmoji($shortcode, $char); }
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
// ===========================================================================
|
|
219
|
+
// HTML — formatter
|
|
220
|
+
// ===========================================================================
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Re-indents an HTML string (PHP 8.4 `Dom\HTMLDocument` / Lexbor, 4-space
|
|
224
|
+
* indent). `<script>` / `<style>` bodies are re-indented but not reformatted.
|
|
225
|
+
*
|
|
226
|
+
* @param string $html Source HTML.
|
|
227
|
+
* @return string Pretty-printed HTML, trailing newline included.
|
|
228
|
+
* @see HTML::format()
|
|
229
|
+
*/
|
|
230
|
+
function html_format(string $html): string { return HTML::format($html); }
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
// ===========================================================================
|
|
234
|
+
// YAML — parser
|
|
235
|
+
//
|
|
236
|
+
// Guarded: yaml_parse() / yaml_parse_file() are also the names of the PECL
|
|
237
|
+
// yaml extension's functions.
|
|
238
|
+
// ===========================================================================
|
|
239
|
+
|
|
240
|
+
if (!function_exists('yaml_parse')) {
|
|
241
|
+
/**
|
|
242
|
+
* Parses a YAML string.
|
|
243
|
+
*
|
|
244
|
+
* @param string $yaml YAML source.
|
|
245
|
+
* @param bool $assoc `true` → mappings as arrays, `false` → `stdClass`.
|
|
246
|
+
* @return mixed
|
|
247
|
+
* @see YAML::parse()
|
|
248
|
+
*/
|
|
249
|
+
function yaml_parse(string $yaml, bool $assoc = false): mixed { return YAML::parse($yaml, $assoc); }
|
|
250
|
+
}
|
|
251
|
+
if (!function_exists('yaml_parse_file')) {
|
|
252
|
+
/**
|
|
253
|
+
* Parses a YAML file.
|
|
254
|
+
*
|
|
255
|
+
* @param string $path Path to the `.yaml` / `.yml` file.
|
|
256
|
+
* @param bool $assoc `true` → arrays, `false` → `stdClass`.
|
|
257
|
+
* @return mixed
|
|
258
|
+
* @throws \RuntimeException When the file cannot be read.
|
|
259
|
+
* @see YAML::parseFile()
|
|
260
|
+
*/
|
|
261
|
+
function yaml_parse_file(string $path, bool $assoc = false): mixed { return YAML::parseFile($path, $assoc); }
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Parses a YAML/JSON file, then recursively inlines any string value that
|
|
266
|
+
* points to an existing relative `.yaml` / `.yml` / `.json` file.
|
|
267
|
+
*
|
|
268
|
+
* @param string $path Path to the root file.
|
|
269
|
+
* @param bool $assoc `true` → arrays, `false` → `stdClass`.
|
|
270
|
+
* @return mixed
|
|
271
|
+
* @throws \RuntimeException On an unreadable file or a circular reference.
|
|
272
|
+
* @see YAML::loadFile()
|
|
273
|
+
*/
|
|
274
|
+
function yaml_load_file(string $path, bool $assoc = false): mixed { return YAML::loadFile($path, $assoc); }
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
// ===========================================================================
|
|
278
|
+
// SCHEMA — pure-PHP JSON Schema validator (instance class)
|
|
279
|
+
// ===========================================================================
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Builds a JSON Schema validator (Draft-7-ish, Ajv-like API).
|
|
283
|
+
*
|
|
284
|
+
* $v = schema($schema);
|
|
285
|
+
* if (!$v->isValid($data)) print_r($v->getErrors());
|
|
286
|
+
*
|
|
287
|
+
* @param array<string,mixed> $schema The root schema.
|
|
288
|
+
* @return SCHEMA
|
|
289
|
+
* @see SCHEMA::__construct()
|
|
290
|
+
*/
|
|
291
|
+
function schema(array $schema): SCHEMA { return new SCHEMA($schema); }
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* One-shot validation. The error list (if any) comes back through `$errors`.
|
|
295
|
+
*
|
|
296
|
+
* if (!schema_validate($schema, $data, $errors)) { … }
|
|
297
|
+
*
|
|
298
|
+
* @param array<string,mixed> $schema The root schema.
|
|
299
|
+
* @param mixed $data Value to validate.
|
|
300
|
+
* @param string[]|null $errors Filled with `"path: message"` strings.
|
|
301
|
+
* @return bool `true` when `$data` is valid.
|
|
302
|
+
* @see SCHEMA::isValid()
|
|
303
|
+
*/
|
|
304
|
+
function schema_validate(array $schema, mixed $data, ?array &$errors = null): bool
|
|
305
|
+
{
|
|
306
|
+
$v = new SCHEMA($schema);
|
|
307
|
+
$ok = $v->isValid($data);
|
|
308
|
+
$errors = $v->getErrors();
|
|
309
|
+
return $ok;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
// ===========================================================================
|
|
314
|
+
// LD — schema.org JSON-LD generator
|
|
315
|
+
// ===========================================================================
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Builds a node and registers it in the page's JSON-LD `@graph`. Any
|
|
319
|
+
* schema.org type is also reachable as `LD::typeName([...])`.
|
|
320
|
+
*
|
|
321
|
+
* ld_add('Recipe', ['name' => 'Tarte', 'recipeYield' => '6']);
|
|
322
|
+
*
|
|
323
|
+
* @param string|string[] $type
|
|
324
|
+
* @param array<string,mixed> $props
|
|
325
|
+
* @param string|null $id Stable `@id` (repeat calls merge).
|
|
326
|
+
* @return array<string,mixed> The stored node.
|
|
327
|
+
* @see LD::add()
|
|
328
|
+
*/
|
|
329
|
+
function ld_add(string|array $type, array $props = [], ?string $id = null): array { return LD::add($type, $props, $id); }
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Builds a bare node (`@type` + pruned `$props`) without touching the graph.
|
|
333
|
+
*
|
|
334
|
+
* @param string|string[] $type
|
|
335
|
+
* @param array<string,mixed> $props
|
|
336
|
+
* @return array<string,mixed>
|
|
337
|
+
* @see LD::node()
|
|
338
|
+
*/
|
|
339
|
+
function ld_node(string|array $type, array $props = []): array { return LD::node($type, $props); }
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* `['@id' => …]` reference to another node (`#organization`, `#website`, …).
|
|
343
|
+
*
|
|
344
|
+
* @param string $id Fragment or absolute URL.
|
|
345
|
+
* @return array{@id:string}
|
|
346
|
+
* @see LD::ref()
|
|
347
|
+
*/
|
|
348
|
+
function ld_ref(string $id): array { return LD::ref($id); }
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Adds the site's main entity (`Organization`, or `jsonld.type`) to the graph.
|
|
352
|
+
*
|
|
353
|
+
* @param array<string,mixed> $overrides
|
|
354
|
+
* @return array<string,mixed>
|
|
355
|
+
* @see LD::organization()
|
|
356
|
+
*/
|
|
357
|
+
function ld_organization(array $overrides = []): array { return LD::organization($overrides); }
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Adds the `Person` behind the site, linked to the `Organization`.
|
|
361
|
+
*
|
|
362
|
+
* @param array<string,mixed> $overrides
|
|
363
|
+
* @return array<string,mixed>
|
|
364
|
+
* @see LD::person()
|
|
365
|
+
*/
|
|
366
|
+
function ld_person(array $overrides = []): array { return LD::person($overrides); }
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Adds the `WebSite` node.
|
|
370
|
+
*
|
|
371
|
+
* @param array<string,mixed> $overrides
|
|
372
|
+
* @return array<string,mixed>
|
|
373
|
+
* @see LD::website()
|
|
374
|
+
*/
|
|
375
|
+
function ld_website(array $overrides = []): array { return LD::website($overrides); }
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Adds the current page's `WebPage` node, built from its PHPDOC.
|
|
379
|
+
*
|
|
380
|
+
* @param array<string,mixed> $overrides
|
|
381
|
+
* @return array<string,mixed>
|
|
382
|
+
* @see LD::webPage()
|
|
383
|
+
*/
|
|
384
|
+
function ld_web_page(array $overrides = []): array { return LD::webPage($overrides); }
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Adds a `BreadcrumbList`. With no `$items` it is derived from the page's
|
|
388
|
+
* ancestor trail (needs `@breadcrumb true`).
|
|
389
|
+
*
|
|
390
|
+
* @param array<int,array{name?:string,url?:string}>|null $items
|
|
391
|
+
* @param array<string,mixed> $overrides
|
|
392
|
+
* @return array<string,mixed>
|
|
393
|
+
* @see LD::breadcrumb()
|
|
394
|
+
*/
|
|
395
|
+
function ld_breadcrumb(?array $items = null, array $overrides = []): array { return LD::breadcrumb($items, $overrides); }
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Adds an `FAQPage` from a `question => answer` map.
|
|
399
|
+
*
|
|
400
|
+
* @param array<string,string|array<string,mixed>> $qa
|
|
401
|
+
* @param array<string,mixed> $overrides
|
|
402
|
+
* @return array<string,mixed>
|
|
403
|
+
* @see LD::faqPage()
|
|
404
|
+
*/
|
|
405
|
+
function ld_faq_page(array $qa, array $overrides = []): array { return LD::faqPage($qa, $overrides); }
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* The `<script type="application/ld+json">…</script>` block for the current
|
|
409
|
+
* graph, or `''`. Calling this from a template places the block by hand and
|
|
410
|
+
* disables the automatic injection.
|
|
411
|
+
*
|
|
412
|
+
* @param bool $pretty
|
|
413
|
+
* @return string
|
|
414
|
+
* @see LD::script()
|
|
415
|
+
*/
|
|
416
|
+
function ld_script(bool $pretty = true): string { return LD::script($pretty); }
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* The JSON-LD document as a string (no `<script>` wrapper).
|
|
420
|
+
*
|
|
421
|
+
* @param bool $pretty
|
|
422
|
+
* @return string
|
|
423
|
+
* @see LD::json()
|
|
424
|
+
*/
|
|
425
|
+
function ld_json(bool $pretty = true): string { return LD::json($pretty); }
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
// ===========================================================================
|
|
429
|
+
// CACHE — persistent key/value (SQLite, `.cache.db` at the project root)
|
|
430
|
+
// ===========================================================================
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Reads a cached value.
|
|
434
|
+
*
|
|
435
|
+
* @param string $key Cache key.
|
|
436
|
+
* @return mixed The stored value, or `null` when missing or expired.
|
|
437
|
+
* @see CACHE::get()
|
|
438
|
+
*/
|
|
439
|
+
function cache_get(string $key): mixed { return CACHE::get($key); }
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Writes a cached value.
|
|
443
|
+
*
|
|
444
|
+
* @param string $key Cache key.
|
|
445
|
+
* @param mixed $val Value to store (serialized).
|
|
446
|
+
* @param int $ttl Lifetime in seconds; `0` = forever.
|
|
447
|
+
* @return bool `true` on success.
|
|
448
|
+
* @see CACHE::set()
|
|
449
|
+
*/
|
|
450
|
+
function cache_set(string $key, mixed $val, int $ttl = 0): bool { return CACHE::set($key, $val, $ttl); }
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Deletes a single cache entry.
|
|
454
|
+
*
|
|
455
|
+
* @param string $key Cache key.
|
|
456
|
+
* @return bool `true` on success.
|
|
457
|
+
* @see CACHE::delete()
|
|
458
|
+
*/
|
|
459
|
+
function cache_delete(string $key): bool { return CACHE::delete($key); }
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Drops every expired entry from the cache.
|
|
463
|
+
*
|
|
464
|
+
* @return bool `true` on success.
|
|
465
|
+
* @see CACHE::purge()
|
|
466
|
+
*/
|
|
467
|
+
function cache_purge(): bool { return CACHE::purge(); }
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
// ===========================================================================
|
|
471
|
+
// IMG — image autogenerator
|
|
472
|
+
// ===========================================================================
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Resolves `$path` against `image.source`, generates a resized copy in
|
|
476
|
+
* `image.dest` (encoded as `image.format`), and returns its URL relative to
|
|
477
|
+
* the file that called this function. Re-uses the existing file when it is
|
|
478
|
+
* newer than the source.
|
|
479
|
+
*
|
|
480
|
+
* <img src="<?= img_asset('hero.jpg', 800) ?>">
|
|
481
|
+
*
|
|
482
|
+
* @param string $path Source image path, relative to `image.source`.
|
|
483
|
+
* @param int $width Target width in px; `0` keeps the aspect ratio.
|
|
484
|
+
* @param int $height Target height in px; `0` keeps the aspect ratio.
|
|
485
|
+
* @param bool $cover `true` crops to fill `width`×`height` (center),
|
|
486
|
+
* `false` fits inside.
|
|
487
|
+
* @return string URL of the generated image, relative to the caller.
|
|
488
|
+
* @see IMG::asset()
|
|
489
|
+
*/
|
|
490
|
+
function img_asset(string $path, int $width = 0, int $height = 0, bool $cover = false): string
|
|
491
|
+
{
|
|
492
|
+
// Forward the real caller's path so IMG::asset() builds the URL
|
|
493
|
+
// relative to the template, not to this wrapper file.
|
|
494
|
+
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
|
|
495
|
+
return IMG::asset($path, $width, $height, $cover, $trace[0]['file'] ?? '');
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Extracts the most representative colors from an image (median-cut in Lab
|
|
500
|
+
* space, near-white/near-black excluded). CACHE-backed.
|
|
501
|
+
*
|
|
502
|
+
* @param string $path Source image path, relative to `image.source`.
|
|
503
|
+
* @param int $colors Number of colors to return.
|
|
504
|
+
* @return string[] Colors as `#rrggbb`, most frequent first.
|
|
505
|
+
* @see IMG::palette()
|
|
506
|
+
*/
|
|
507
|
+
function img_palette(string $path, int $colors = 5): array { return IMG::palette($path, $colors); }
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
// ===========================================================================
|
|
511
|
+
// FS — filesystem helpers
|
|
512
|
+
// ===========================================================================
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Recursively walks a directory, yielding every file whose name matches the
|
|
516
|
+
* glob pattern in the last path segment.
|
|
517
|
+
*
|
|
518
|
+
* foreach (fs_dig('src/*.php') as $file) { … }
|
|
519
|
+
*
|
|
520
|
+
* @param string $path Directory + glob pattern (e.g. `src/_index.php`).
|
|
521
|
+
* @return iterable<string> File paths.
|
|
522
|
+
* @see FS::dig()
|
|
523
|
+
*/
|
|
524
|
+
function fs_dig(string $path): iterable { return FS::dig($path); }
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Computes the relative path from one location to another (directories are
|
|
528
|
+
* detected and treated as such). Both accept `/` or `\` separators.
|
|
529
|
+
*
|
|
530
|
+
* @param string $from Source path.
|
|
531
|
+
* @param string $to Target path.
|
|
532
|
+
* @return string Relative path, `/`-separated.
|
|
533
|
+
* @see FS::getRelativePath()
|
|
534
|
+
*/
|
|
535
|
+
function fs_get_relative_path(string $from, string $to): string { return FS::getRelativePath($from, $to); }
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Parses the first PHPDoc block of a PHP file into an object of `@tag`
|
|
539
|
+
* values.
|
|
540
|
+
*
|
|
541
|
+
* @param string $file Path to the PHP file.
|
|
542
|
+
* @return object|false `stdClass` of tag → value, or false when the
|
|
543
|
+
* file cannot be resolved.
|
|
544
|
+
* @see FS::phpFileInfo()
|
|
545
|
+
*/
|
|
546
|
+
function fs_php_file_info(string $file): object|bool { return FS::phpFileInfo($file); }
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Immediate child pages of the page being rendered, ordered by `@position`
|
|
550
|
+
* ascending (a missing `@position` counts as 999999), then by folder name.
|
|
551
|
+
*
|
|
552
|
+
* Scans the folders directly below the current page's directory, keeps the
|
|
553
|
+
* ones holding an `_index.php`, and returns one `stdClass` per child: the
|
|
554
|
+
* parsed PHPDOC of that `_index.php`, plus a `->file` key with its absolute
|
|
555
|
+
* path. Works the same from a template, a layout partial or a helper.
|
|
556
|
+
* Only usable during a render.
|
|
557
|
+
*
|
|
558
|
+
* foreach (fs_get_children() as $page) { echo $page->title, $page->file; }
|
|
559
|
+
*
|
|
560
|
+
* @return object[]
|
|
561
|
+
* @throws \Exception When called outside of a render.
|
|
562
|
+
* @see FS::getChildren()
|
|
563
|
+
*/
|
|
564
|
+
function fs_get_children(): array
|
|
565
|
+
{
|
|
566
|
+
// Anchored on the page under render (PREPROS::$file), so it behaves the
|
|
567
|
+
// same whether it's called from the template, a layout partial, or a
|
|
568
|
+
// helper function.
|
|
569
|
+
return FS::getChildren();
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Breadcrumb trail of the page being rendered: one `stdClass` per ancestor
|
|
574
|
+
* page, ordered from the top-most ancestor down to the nearest parent (the
|
|
575
|
+
* parsed PHPDOC of each `_index.php`, plus a `->file` key with its absolute
|
|
576
|
+
* path).
|
|
577
|
+
*
|
|
578
|
+
* Returns `[]` unless the current page opts in with `@breadcrumb true` (or
|
|
579
|
+
* `1`). The current page is never part of its own trail; the walk climbs the
|
|
580
|
+
* parent folders and stops at the source root, or at the first ancestor
|
|
581
|
+
* `_index.php` with no active `@breadcrumb` tag (that separator page is left
|
|
582
|
+
* out). Works the same from a template, a layout partial or a helper.
|
|
583
|
+
* Only usable during a render.
|
|
584
|
+
*
|
|
585
|
+
* foreach (fs_get_breadcrumb() as $crumb):
|
|
586
|
+
* <a href="<?= FS::getRelativePath(__DIR__, dirname($crumb->file)) ?>/"><?= $crumb->title ?></a>
|
|
587
|
+
* endforeach
|
|
588
|
+
*
|
|
589
|
+
* @return object[]
|
|
590
|
+
* @throws \Exception When called outside of a render.
|
|
591
|
+
* @see FS::getBreadcrumb()
|
|
592
|
+
*/
|
|
593
|
+
function fs_get_breadcrumb(): array
|
|
594
|
+
{
|
|
595
|
+
// Anchored on the page under render (PREPROS::$file), so it behaves the
|
|
596
|
+
// same whether it's called from the template, a layout partial, or a
|
|
597
|
+
// helper function.
|
|
598
|
+
return FS::getBreadcrumb();
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Recursively deletes a directory.
|
|
603
|
+
*
|
|
604
|
+
* @param string $dir Directory to empty.
|
|
605
|
+
* @param bool $removeSelf `true` also removes `$dir` itself; `false`
|
|
606
|
+
* leaves it empty.
|
|
607
|
+
* @return bool `true` on success.
|
|
608
|
+
* @see FS::rmdir()
|
|
609
|
+
*/
|
|
610
|
+
function fs_rmdir(string $dir, bool $removeSelf = true): bool { return FS::rmdir($dir, $removeSelf); }
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Joins path segments with `/`, resolving `.` and `..`. URL-aware: a
|
|
614
|
+
* leading scheme/host is preserved.
|
|
615
|
+
*
|
|
616
|
+
* @param string ...$parts Path segments.
|
|
617
|
+
* @return string The joined path.
|
|
618
|
+
* @see FS::pathJoin()
|
|
619
|
+
*/
|
|
620
|
+
function fs_path_join(string ...$parts): string { return FS::pathJoin(...$parts); }
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
// ===========================================================================
|
|
624
|
+
// STR — string helpers
|
|
625
|
+
// ===========================================================================
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Escapes a string for HTML output (`htmlspecialchars`, `ENT_QUOTES`, UTF-8).
|
|
629
|
+
*
|
|
630
|
+
* @param string $str Raw string.
|
|
631
|
+
* @return string HTML-safe string.
|
|
632
|
+
* @see STR::htmlesc()
|
|
633
|
+
*/
|
|
634
|
+
function str_htmlesc(string $str): string { return STR::htmlesc($str); }
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Replaces every `<$tag …>…</$tag>` (paired, self-closing or opening-only)
|
|
638
|
+
* in `$contents` with the callback's return value. This is the engine
|
|
639
|
+
* behind {@see prepros_register_tag()}.
|
|
640
|
+
*
|
|
641
|
+
* @param string $tag Tag name, without angle brackets.
|
|
642
|
+
* @param string $contents Haystack.
|
|
643
|
+
* @param callable $clb `function(string $fullMatch, array $attrs, string $inner): string`
|
|
644
|
+
* @return string `$contents` with every match replaced.
|
|
645
|
+
* @see STR::replaceTags()
|
|
646
|
+
*/
|
|
647
|
+
function str_replace_tags(string $tag, string $contents, callable $clb): string { return STR::replaceTags($tag, $contents, $clb); }
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Parses an HTML attribute string into an associative array (boolean
|
|
651
|
+
* attributes map to `true`).
|
|
652
|
+
*
|
|
653
|
+
* @param string $attributes e.g. `href="/x" target="_blank" hidden`
|
|
654
|
+
* @return array<string,string|true>
|
|
655
|
+
* @see STR::parseHtmlAttributes()
|
|
656
|
+
*/
|
|
657
|
+
function str_parse_html_attributes(string $attributes): array { return STR::parseHtmlAttributes($attributes); }
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Removes the common leading whitespace shared by every non-blank line.
|
|
661
|
+
*
|
|
662
|
+
* @param string $str Indented text.
|
|
663
|
+
* @return string De-indented text.
|
|
664
|
+
* @see STR::trimIndent()
|
|
665
|
+
*/
|
|
666
|
+
function str_trim_indent(string $str): string { return STR::trimIndent($str); }
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Tells whether a string is an URL with a recognized scheme
|
|
670
|
+
* (http, https, ftp, ftps, ssh, ssl, sftp, itunes).
|
|
671
|
+
*
|
|
672
|
+
* @param string $str Candidate string.
|
|
673
|
+
* @return bool
|
|
674
|
+
* @see STR::is_url()
|
|
675
|
+
*/
|
|
676
|
+
function str_is_url(string $str): bool { return STR::is_url($str); }
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Trims, then decodes HTML entities (`ENT_QUOTES`, UTF-8).
|
|
680
|
+
*
|
|
681
|
+
* @param string $str String possibly containing entities.
|
|
682
|
+
* @return string Decoded string.
|
|
683
|
+
* @see STR::html_entities_decode()
|
|
684
|
+
*/
|
|
685
|
+
function str_html_entities_decode(string $str): string { return STR::html_entities_decode($str); }
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* Short, stable digest of a string: the first 12 hex chars of its SHA-256.
|
|
689
|
+
*
|
|
690
|
+
* @param string $str Input.
|
|
691
|
+
* @return string 12-character hex string.
|
|
692
|
+
* @see STR::shorthash()
|
|
693
|
+
*/
|
|
694
|
+
function str_shorthash(string $str): string { return STR::shorthash($str); }
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Unicode-normalizes a string to NFD and strips combining marks
|
|
698
|
+
* (`é` → `e`, `ü` → `u`).
|
|
699
|
+
*
|
|
700
|
+
* @param string $str Input.
|
|
701
|
+
* @return string ASCII-folded string.
|
|
702
|
+
* @see STR::normalize()
|
|
703
|
+
*/
|
|
704
|
+
function str_normalize(string $str): string { return STR::normalize($str); }
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* Turns a string into a slug: normalized, lowercased, non-alphanumerics
|
|
708
|
+
* replaced by `$sep`.
|
|
709
|
+
*
|
|
710
|
+
* @param string $str Input.
|
|
711
|
+
* @param string $sep Separator; `''` → compact id, `'-'` → hyphenated slug.
|
|
712
|
+
* @return string
|
|
713
|
+
* @see STR::slug()
|
|
714
|
+
*/
|
|
715
|
+
function str_slug(string $str, string $sep = ''): string { return STR::slug($str, $sep); }
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
// ===========================================================================
|
|
719
|
+
// ARR — array helpers
|
|
720
|
+
// ===========================================================================
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Depth-first search through a nested array/object structure; returns the
|
|
724
|
+
* value of the first entry keyed `$key` at any depth, or `null`.
|
|
725
|
+
*
|
|
726
|
+
* @param mixed $data Array or object to search.
|
|
727
|
+
* @param string $key Key to look for.
|
|
728
|
+
* @return mixed The matched value, or `null`.
|
|
729
|
+
* @see ARR::find_key()
|
|
730
|
+
*/
|
|
731
|
+
function arr_find_key(mixed $data, string $key): mixed { return ARR::find_key($data, $key); }
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
// ===========================================================================
|
|
735
|
+
// CURL — network (only usable when kirigami.yaml `network: true`)
|
|
736
|
+
// ===========================================================================
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Checks whether an URL is reachable (HEAD request, follows redirects).
|
|
740
|
+
*
|
|
741
|
+
* @param string $url URL to probe.
|
|
742
|
+
* @param string|null $mimereg Reserved (currently unused).
|
|
743
|
+
* @return bool `true` on a 2xx/3xx response.
|
|
744
|
+
* @see CURL::urlExists()
|
|
745
|
+
*/
|
|
746
|
+
function curl_url_exists(string $url, $mimereg = null) { return CURL::urlExists($url, $mimereg); }
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Returns `curl_getinfo()` for an URL (HEAD request).
|
|
750
|
+
*
|
|
751
|
+
* @param string $url URL to probe.
|
|
752
|
+
* @return array|false The info array, or false for a non-URL.
|
|
753
|
+
* @see CURL::getInfo()
|
|
754
|
+
*/
|
|
755
|
+
function curl_get_info(string $url) { return CURL::getInfo($url); }
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* Fetches an URL, browser-like headers and a shared cookie jar included.
|
|
759
|
+
* A drop-in replacement for `file_get_contents()` on remote URLs.
|
|
760
|
+
*
|
|
761
|
+
* @param string $file URL to fetch.
|
|
762
|
+
* @param string|null $dest Optional local path to stream the body into;
|
|
763
|
+
* when set, the function returns a bool instead
|
|
764
|
+
* of the body.
|
|
765
|
+
* @param callable|null $clb Optional progress callback `function(float $ratio): void`
|
|
766
|
+
* (`$ratio` between 0 and 1).
|
|
767
|
+
* @return string|bool Response body, or (with `$dest`) `true`/`false`.
|
|
768
|
+
* @see CURL::getContents()
|
|
769
|
+
*/
|
|
770
|
+
function curl_get_contents(string $file, $dest = null, $clb = null) { return CURL::getContents($file, $dest, $clb); }
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
// ===========================================================================
|
|
774
|
+
// SCRAPER — page metadata (CACHE-backed)
|
|
775
|
+
// ===========================================================================
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Scrapes an URL for its social/meta card.
|
|
779
|
+
*
|
|
780
|
+
* @param string $url Page URL.
|
|
781
|
+
* @return object|false `stdClass` with `->title ->description ->image
|
|
782
|
+
* ->label ->url ->key`, or false when the page has
|
|
783
|
+
* no usable title.
|
|
784
|
+
* @throws \Exception When the page cannot be crawled.
|
|
785
|
+
* @see SCRAPER::get()
|
|
786
|
+
*/
|
|
787
|
+
function scraper_get(string $url) { return SCRAPER::get($url); }
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
// ===========================================================================
|
|
791
|
+
// OBF — reversible payload obfuscation
|
|
792
|
+
// ===========================================================================
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* Obfuscates a value: JSON → base64 → ROT-13 → gzip (2-byte header dropped).
|
|
796
|
+
* Not encryption — just makes an embedded payload non-obvious.
|
|
797
|
+
*
|
|
798
|
+
* @param mixed $obj Any JSON-serializable value.
|
|
799
|
+
* @return string Obfuscated blob.
|
|
800
|
+
* @see OBF::encode()
|
|
801
|
+
*/
|
|
802
|
+
function obf_encode(mixed $obj): string { return OBF::encode($obj); }
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* Reverses {@see obf_encode()}.
|
|
806
|
+
*
|
|
807
|
+
* @param string $str Blob produced by `obf_encode()`.
|
|
808
|
+
* @return mixed The original value.
|
|
809
|
+
* @see OBF::decode()
|
|
810
|
+
*/
|
|
811
|
+
function obf_decode(string $str): mixed { return OBF::decode($str); }
|
|
812
|
+
|
|
813
|
+
|
|
814
|
+
// ===========================================================================
|
|
815
|
+
// STD — build-result output (these terminate the process)
|
|
816
|
+
// ===========================================================================
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* Writes a success result (exported files + `$props`) as JSON and exits 0.
|
|
820
|
+
*
|
|
821
|
+
* @param array<string,mixed>|string $props Extra fields, or a plain message
|
|
822
|
+
* string (stored as `message`).
|
|
823
|
+
* @return never
|
|
824
|
+
* @see STD::succeed()
|
|
825
|
+
*/
|
|
826
|
+
function std_succeed($props = []): never { STD::succeed($props); exit(0); }
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* Writes an error result as JSON and exits 1.
|
|
830
|
+
*
|
|
831
|
+
* @param array<string,mixed>|string $props Extra fields, or a plain message
|
|
832
|
+
* string (stored as `error`).
|
|
833
|
+
* @return never
|
|
834
|
+
* @see STD::error()
|
|
835
|
+
*/
|
|
836
|
+
function std_error($props = []): never { STD::error($props); exit(1); }
|