@kirigami/php-prepros 1.9.3 → 3.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 +502 -21
- package/README.md +1988 -1817
- package/index.d.ts +62 -26
- package/index.js +1 -1
- package/package.json +59 -59
- package/src/imagebatch.php +68 -67
- package/src/libraries/aliases.inc.php +879 -879
- package/src/libraries/curl.class.php +7 -7
- package/src/libraries/ld.class.php +803 -804
- package/src/libraries/md-legacy.class.php +1257 -0
- package/src/libraries/md.class.php +51 -1249
- package/src/libraries/meta.class.php +496 -506
- package/src/libraries/{normalizer.class.php → normalizer-legacy.class.php} +11 -1
- package/src/libraries/prepros.class.php +85 -8
- package/src/libraries/schema-legacy.class.php +549 -0
- package/src/libraries/schema.class.php +99 -472
- package/src/libraries/yaml-legacy.class.php +598 -0
- package/src/libraries/yaml.class.php +41 -406
- package/src/prepros.js +152 -48
- package/src/prepros.php +4 -5
- package/src/runenv.php +2 -2
- package/src/utils.inc.php +12 -2
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
<?php
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* NORMALIZER_LEGACY — pure-PHP Unicode normalizer (a port of
|
|
5
|
+
* symfony/polyfill-intl-normalizer), with the standard Normalizer API.
|
|
6
|
+
*
|
|
7
|
+
* Retired: ext-intl isn't in the WASM build, but the native `norm` extension
|
|
8
|
+
* (php-kirigami/php-norm, utf8proc, statically built into @kirigami/php-wasm)
|
|
9
|
+
* now provides the real `Normalizer` class and `normalizer_*()` functions.
|
|
10
|
+
* Kept here as a reference implementation and an easy rollback path;
|
|
11
|
+
* autoloadable, but not used anywhere by default.
|
|
12
|
+
*/
|
|
13
|
+
class NORMALIZER_LEGACY
|
|
4
14
|
{
|
|
5
15
|
const NONE = 2;
|
|
6
16
|
const FORM_D = 4;
|
|
@@ -15,6 +15,8 @@ final class PREPROS
|
|
|
15
15
|
public static function loadConfig(object $config)
|
|
16
16
|
{
|
|
17
17
|
self::$config = $config;
|
|
18
|
+
self::$config->before ??= null;
|
|
19
|
+
self::$config->after ??= null;
|
|
18
20
|
self::$root = realpath($config->root) . S;
|
|
19
21
|
if (!empty($config->includes)) foreach ($config->includes as $path) {
|
|
20
22
|
if (!is_file(realpath(self::$root . $path))) continue;
|
|
@@ -28,6 +30,25 @@ final class PREPROS
|
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
|
|
33
|
+
// Only page files beneath the configured source root are publishable.
|
|
34
|
+
// Check every relative directory, but not the configured root's own name.
|
|
35
|
+
public static function isPage(string $file): bool
|
|
36
|
+
{
|
|
37
|
+
$file = realpath($file);
|
|
38
|
+
if (!$file || !is_file($file)) return false;
|
|
39
|
+
$file = str_replace('\\', '/', $file);
|
|
40
|
+
$root = str_replace('\\', '/', self::$root);
|
|
41
|
+
if (!str_starts_with($file, $root)) return false;
|
|
42
|
+
$parts = explode('/', substr($file, strlen($root)));
|
|
43
|
+
$name = array_pop($parts);
|
|
44
|
+
if (!preg_match('/^_.*\.php$/i', $name)) return false;
|
|
45
|
+
foreach ($parts as $part) {
|
|
46
|
+
if (str_starts_with($part, '_')) return false;
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
31
52
|
public static function render(string $file)
|
|
32
53
|
{
|
|
33
54
|
$dir = pathinfo($file, PATHINFO_DIRNAME) . S;
|
|
@@ -67,6 +88,20 @@ final class PREPROS
|
|
|
67
88
|
}, explode(PHP_EOL, $body)));
|
|
68
89
|
}
|
|
69
90
|
|
|
91
|
+
$typeConfig = !empty($type) ? (self::$config->types->{$type} ?? null) : null;
|
|
92
|
+
|
|
93
|
+
ob_start();
|
|
94
|
+
self::processHook('pre_type_before', $typeConfig->before ?? null);
|
|
95
|
+
if (!empty($typeConfig->before)) include(realpath(self::$root . $typeConfig->before));
|
|
96
|
+
$typeHeader = self::processHook('post_type_before', ob_get_clean());
|
|
97
|
+
|
|
98
|
+
ob_start();
|
|
99
|
+
self::processHook('pre_type_after', $typeConfig->after ?? null);
|
|
100
|
+
if (!empty($typeConfig->after)) include(realpath(self::$root . $typeConfig->after));
|
|
101
|
+
$typeFooter = self::processHook('post_type_after', ob_get_clean());
|
|
102
|
+
|
|
103
|
+
$body = $typeHeader . $body . $typeFooter;
|
|
104
|
+
|
|
70
105
|
ob_start();
|
|
71
106
|
self::processHook('pre_after', self::$config->after);
|
|
72
107
|
if (self::$config->after) include(realpath(self::$root . self::$config->after));
|
|
@@ -96,9 +131,7 @@ final class PREPROS
|
|
|
96
131
|
$paths = [];
|
|
97
132
|
$root = realpath(self::$root);
|
|
98
133
|
foreach (FS::dig($root . '/_index.php') as $file) {
|
|
99
|
-
|
|
100
|
-
if (strpos($parent, '_') === 0) continue;
|
|
101
|
-
if (strpos(pathinfo($file, PATHINFO_FILENAME), '_') !== 0) continue;
|
|
134
|
+
if (!self::isPage($file)) continue;
|
|
102
135
|
$paths[] = str_replace('\\', '/', ltrim(str_replace($root, '', pathinfo(realpath($file), PATHINFO_DIRNAME)), DIRECTORY_SEPARATOR));
|
|
103
136
|
}
|
|
104
137
|
if (empty($paths)) $paths[] = '';
|
|
@@ -127,12 +160,40 @@ final class PREPROS
|
|
|
127
160
|
$destrobots = self::$root . 'robots.txt';
|
|
128
161
|
$urlrobots = rtrim(self::$config->data->baseurl, '/') . '/sitemap.xml';
|
|
129
162
|
file_put_contents($destrobots, "User-agent: *\nAllow: /\nSitemap: {$urlrobots}");
|
|
130
|
-
|
|
131
|
-
self::
|
|
163
|
+
|
|
164
|
+
$desthumans = self::humans();
|
|
165
|
+
|
|
166
|
+
self::exportFile(array_filter([$dest, $destrobots, $desthumans]));
|
|
132
167
|
return realpath($dest);
|
|
133
168
|
}
|
|
134
169
|
|
|
135
170
|
|
|
171
|
+
// Generates humans.txt (humanstxt.org) from the kirigami: block's
|
|
172
|
+
// author/email — same source fields config.js's fillBanner() already
|
|
173
|
+
// pulls ###AUTHOR###/###EMAIL### from, same spirit: fill in what the
|
|
174
|
+
// project already declared instead of asking for it twice. Skipped
|
|
175
|
+
// entirely when neither is set — nothing meaningful to write.
|
|
176
|
+
private static function humans(): ?string
|
|
177
|
+
{
|
|
178
|
+
$author = trim((string) (self::$config->data->author ?? ''));
|
|
179
|
+
$email = trim((string) (self::$config->data->email ?? ''));
|
|
180
|
+
if ($author === '' && $email === '') return null;
|
|
181
|
+
|
|
182
|
+
$lines = ['/* TEAM */', ''];
|
|
183
|
+
if ($author !== '') $lines[] = " {$author}";
|
|
184
|
+
if ($email !== '') $lines[] = " Contact: {$email}";
|
|
185
|
+
$lines[] = '';
|
|
186
|
+
$lines[] = '/* SITE */';
|
|
187
|
+
$lines[] = '';
|
|
188
|
+
$lines[] = ' Last update: ' . date('Y-m-d');
|
|
189
|
+
$lines[] = ' Software: Kirigami -- https://php-kirigami.github.io';
|
|
190
|
+
|
|
191
|
+
$dest = self::$root . 'humans.txt';
|
|
192
|
+
file_put_contents($dest, implode("\n", $lines) . "\n");
|
|
193
|
+
return $dest;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
|
|
136
197
|
/**
|
|
137
198
|
* Auto-wires each page: a theme/FOUC guard as the first child of <head>, a
|
|
138
199
|
* <link> for every `sass` task output, a <script> (no `defer`, before
|
|
@@ -146,6 +207,22 @@ final class PREPROS
|
|
|
146
207
|
* only expanded on export (see replaceTokens()), so rebuilding a preview
|
|
147
208
|
* never rewrites the committed page.
|
|
148
209
|
*/
|
|
210
|
+
// Whether $contents already has a real <$tag … $attr="…$needle…"> —
|
|
211
|
+
// not just a mention of the filename somewhere in the page (prose, a
|
|
212
|
+
// <code> block documenting real build output, an HTML comment). The
|
|
213
|
+
// naive str_contains() this replaces matched any occurrence anywhere,
|
|
214
|
+
// so a page that merely *documents* the compiled filename (e.g. a
|
|
215
|
+
// docs page showing captured `kiri build` output) got its real
|
|
216
|
+
// <link>/<script> injection skipped — found in production on
|
|
217
|
+
// php-kirigami.github.io's own docs/cli page.
|
|
218
|
+
private static function hasAssetTag(string $contents, string $tag, string $attr, string $needle): bool
|
|
219
|
+
{
|
|
220
|
+
$pattern = '/<' . $tag . '\b[^>]*\b' . $attr . '\s*=\s*["\'][^"\']*'
|
|
221
|
+
. preg_quote($needle, '/') . '[^"\']*["\'][^>]*>/i';
|
|
222
|
+
return (bool) preg_match($pattern, $contents);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
|
|
149
226
|
private static function injectHead(string $contents, string $relroot): string
|
|
150
227
|
{
|
|
151
228
|
$inject = [];
|
|
@@ -180,12 +257,12 @@ final class PREPROS
|
|
|
180
257
|
|
|
181
258
|
if (($task['type'] ?? '') === 'sass') {
|
|
182
259
|
$out = preg_replace('/\.s?css$/', '.min.css', $entry);
|
|
183
|
-
if ($out !== $entry && !
|
|
260
|
+
if ($out !== $entry && !self::hasAssetTag($contents, 'link', 'href', $out)) {
|
|
184
261
|
$links[] = '<link rel="stylesheet" href="' . $relroot . $out . '?###TIMESTAMP###">';
|
|
185
262
|
}
|
|
186
263
|
} elseif (($task['type'] ?? '') === 'esbuild') {
|
|
187
264
|
$out = preg_replace('/\.[jt]s$/', '.min.js', $entry);
|
|
188
|
-
if ($out !== $entry && !
|
|
265
|
+
if ($out !== $entry && !self::hasAssetTag($contents, 'script', 'src', $out)) {
|
|
189
266
|
$scripts[] = '<script src="' . $relroot . $out . '?###TIMESTAMP###"></script>';
|
|
190
267
|
}
|
|
191
268
|
}
|
|
@@ -325,4 +402,4 @@ final class PREPROS
|
|
|
325
402
|
}
|
|
326
403
|
|
|
327
404
|
|
|
328
|
-
include_once(__DIR__ . '/prepros.plugins.php');
|
|
405
|
+
include_once(__DIR__ . '/prepros.plugins.php');
|