@kirigami/php-prepros 1.0.1 → 1.0.3
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 +4 -0
- package/package.json +5 -5
- package/src/libraries/html.class.php +58 -6
- package/src/libraries/md.class.php +238 -55
- package/src/libraries/yaml.class.php +131 -0
- package/src/prepros.js +52 -33
- package/src/prepros.php +2 -2
package/README.md
CHANGED
|
@@ -53,6 +53,7 @@ Part of the **Kirigami** project ecosystem. Other packages are coming soon.
|
|
|
53
53
|
|
|
54
54
|
---
|
|
55
55
|
|
|
56
|
+
|
|
56
57
|
## How it works
|
|
57
58
|
|
|
58
59
|
`@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.
|
|
@@ -114,6 +115,9 @@ Source pages live in the directory pointed to by `kirigami.root`. The naming con
|
|
|
114
115
|
|
|
115
116
|
```
|
|
116
117
|
src/
|
|
118
|
+
├── _layout/
|
|
119
|
+
├── _lib/
|
|
120
|
+
├── about/
|
|
117
121
|
├── _index.php → src/index.html
|
|
118
122
|
├── about/
|
|
119
123
|
│ └── _index.php → src/about/index.html
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kirigami/php-prepros",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
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,14 +44,14 @@
|
|
|
44
44
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@kirigami/php-wasm": "8.5.
|
|
48
|
-
"
|
|
47
|
+
"@kirigami/php-wasm": "8.5.9",
|
|
48
|
+
"@kirigami/struct-walker": "1.0.2"
|
|
49
49
|
},
|
|
50
50
|
"repository": {
|
|
51
51
|
"type": "git",
|
|
52
|
-
"url": "https://github.com/php-kirigami/kirigami"
|
|
52
|
+
"url": "git+https://github.com/php-kirigami/kirigami.git"
|
|
53
53
|
},
|
|
54
|
-
"homepage": "https://github.com/php-kirigami/kirigami
|
|
54
|
+
"homepage": "https://github.com/php-kirigami/kirigami/tree/main/packages/php-prepros#readme",
|
|
55
55
|
"bugs": {
|
|
56
56
|
"url": "https://github.com/php-kirigami/kirigami/issues"
|
|
57
57
|
}
|
|
@@ -101,9 +101,13 @@ class HTML
|
|
|
101
101
|
return "{$pad}<{$tag}{$attrs}></{$tag}>\n";
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
// Si tous les enfants sont inline
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
// Si tous les enfants sont inline ET que ça ressemble à un flux de texte
|
|
105
|
+
// (du vrai texte, ou un seul élément enfant), on re-sérialise en une seule ligne.
|
|
106
|
+
// Sans looksLikeTextFlow, un <section> qui contient plusieurs gros <a> côte à côte
|
|
107
|
+
// (ex: une grille de logos) se retrouverait aussi collé sur une seule ligne,
|
|
108
|
+
// puisque <a> est dans INLINE — ce n'est pas ce qu'on veut.
|
|
109
|
+
if (static::hasOnlyInlineChildren($node) && static::looksLikeTextFlow($node)) {
|
|
110
|
+
$inner = trim(static::renderInline($node));
|
|
107
111
|
return "{$pad}<{$tag}{$attrs}>{$inner}</{$tag}>\n";
|
|
108
112
|
}
|
|
109
113
|
|
|
@@ -115,6 +119,53 @@ class HTML
|
|
|
115
119
|
return "{$pad}<{$tag}{$attrs}>\n{$inner}{$pad}</{$tag}>\n";
|
|
116
120
|
}
|
|
117
121
|
|
|
122
|
+
// Sérialise le contenu inline d'un nœud sur une seule ligne, en réduisant
|
|
123
|
+
// tout groupe d'espaces/tabs/retours à la ligne du texte source à une seule espace
|
|
124
|
+
// (équivalent au comportement de collapse des espaces en HTML).
|
|
125
|
+
private static function renderInline(Dom\Node $node): string
|
|
126
|
+
{
|
|
127
|
+
$out = '';
|
|
128
|
+
foreach ($node->childNodes as $child) {
|
|
129
|
+
if ($child->nodeType === XML_TEXT_NODE) {
|
|
130
|
+
$text = preg_replace('/\s+/', ' ', $child->nodeValue);
|
|
131
|
+
$out .= htmlspecialchars($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if ($child->nodeType === XML_COMMENT_NODE) {
|
|
135
|
+
$out .= '<!--' . $child->nodeValue . '-->';
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if ($child->nodeType !== XML_ELEMENT_NODE) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
$tag = strtolower($child->nodeName);
|
|
142
|
+
$attrs = static::renderAttrs($child);
|
|
143
|
+
if (in_array($tag, self::VOID, true)) {
|
|
144
|
+
$out .= "<{$tag}{$attrs}>";
|
|
145
|
+
} else {
|
|
146
|
+
$out .= "<{$tag}{$attrs}>" . static::renderInline($child) . "</{$tag}>";
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return $out;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Distingue "du texte qui contient un peu d'inline" (Cliquez <a>ici</a>.) d'un
|
|
153
|
+
// conteneur qui aligne simplement plusieurs blocs inline côte à côte (grille de <a><img></a>).
|
|
154
|
+
// Vrai si : il y a du texte significatif parmi les enfants, OU un seul enfant élément.
|
|
155
|
+
private static function looksLikeTextFlow(Dom\Node $node): bool
|
|
156
|
+
{
|
|
157
|
+
$elementCount = 0;
|
|
158
|
+
foreach ($node->childNodes as $child) {
|
|
159
|
+
if ($child->nodeType === XML_TEXT_NODE && trim($child->nodeValue) !== '') {
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
if ($child->nodeType === XML_ELEMENT_NODE) {
|
|
163
|
+
$elementCount++;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return $elementCount <= 1;
|
|
167
|
+
}
|
|
168
|
+
|
|
118
169
|
// Vérifie que tous les descendants directs sont inline (texte, void inline, éléments inline)
|
|
119
170
|
// Les éléments inline eux-mêmes ne doivent pas contenir d'éléments block
|
|
120
171
|
private static function hasOnlyInlineChildren(Dom\Node $node): bool
|
|
@@ -165,10 +216,11 @@ class HTML
|
|
|
165
216
|
return $out;
|
|
166
217
|
}
|
|
167
218
|
foreach ($node->attributes as $attr) {
|
|
168
|
-
|
|
169
|
-
|
|
219
|
+
$name = strtolower(trim((string) $attr->name));
|
|
220
|
+
if (in_array($name, self::BOOLEAN_ATTRS, true)) {
|
|
221
|
+
$out .= ' ' . $name;
|
|
170
222
|
} else {
|
|
171
|
-
$out .= ' ' . $
|
|
223
|
+
$out .= ' ' . $name . '="' . htmlspecialchars((string) $attr->value, ENT_QUOTES | ENT_HTML5, 'UTF-8') . '"';
|
|
172
224
|
}
|
|
173
225
|
}
|
|
174
226
|
return $out;
|
|
@@ -64,6 +64,49 @@ class MD {
|
|
|
64
64
|
return array_keys(self::$plugins);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
// ========================================================================
|
|
68
|
+
// Génère un id de type "slug" pour les ancres de titres (ATX et Setext).
|
|
69
|
+
// ========================================================================
|
|
70
|
+
private static function slugify(string $text): string {
|
|
71
|
+
$id = strtolower(preg_replace('/[^\w\- ]/u', '', $text));
|
|
72
|
+
return preg_replace('/\s+/', '-', trim($id));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ========================================================================
|
|
76
|
+
// Convertit une largeur d'indentation (espaces/tabs) en nombre de colonnes,
|
|
77
|
+
// une tabulation comptant pour 4 espaces.
|
|
78
|
+
// ========================================================================
|
|
79
|
+
private static function indentWidth(string $whitespace): int {
|
|
80
|
+
return strlen(str_replace("\t", ' ', $whitespace));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Construit récursivement une liste (imbriquée) <ol>/<ul> à partir d'un
|
|
85
|
+
* tableau plat d'items { indent, type, text }. $i est avancé au fur et à
|
|
86
|
+
* mesure de la consommation des items.
|
|
87
|
+
*
|
|
88
|
+
* @param array<int, array{indent:int, type:string, text:string}> $items
|
|
89
|
+
*/
|
|
90
|
+
private static function buildListTree(array $items, int &$i, int $count): string {
|
|
91
|
+
$type = $items[$i]['type'];
|
|
92
|
+
$baseIndent = $items[$i]['indent'];
|
|
93
|
+
$out = "<{$type}>\n";
|
|
94
|
+
|
|
95
|
+
while ($i < $count && $items[$i]['indent'] === $baseIndent && $items[$i]['type'] === $type) {
|
|
96
|
+
$text = $items[$i]['text'];
|
|
97
|
+
$i++;
|
|
98
|
+
|
|
99
|
+
$nested = '';
|
|
100
|
+
if ($i < $count && $items[$i]['indent'] > $baseIndent) {
|
|
101
|
+
$nested = "\n" . self::buildListTree($items, $i, $count);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
$out .= " <li>{$text}{$nested}</li>\n";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return $out . "</{$type}>";
|
|
108
|
+
}
|
|
109
|
+
|
|
67
110
|
// ========================================================================
|
|
68
111
|
|
|
69
112
|
public static function toHtml(string $markdown): string {
|
|
@@ -137,6 +180,27 @@ class MD {
|
|
|
137
180
|
);
|
|
138
181
|
|
|
139
182
|
|
|
183
|
+
// ====================================================================
|
|
184
|
+
// ÉTAPE 2b : DÉFINITIONS DE LIENS PAR RÉFÉRENCE
|
|
185
|
+
// [label]: https://example.com "Titre optionnel"
|
|
186
|
+
// [label]: <https://example.com> 'Titre optionnel'
|
|
187
|
+
// [label]: https://example.com (Titre optionnel)
|
|
188
|
+
// Extraites (et retirées du texte) avant tout le reste ; utilisées
|
|
189
|
+
// plus loin par les liens [texte][label] / [texte][].
|
|
190
|
+
// ====================================================================
|
|
191
|
+
$refDefs = [];
|
|
192
|
+
$html = preg_replace_callback(
|
|
193
|
+
'/^[ \t]{0,3}\[([^\]]+)\]:[ \t]*<?([^\s>]+)>?(?:[ \t]+(?:"([^"]*)"|\'([^\']*)\'|\(([^)]*)\)))?[ \t]*$/m',
|
|
194
|
+
function ($m) use (&$refDefs): string {
|
|
195
|
+
$label = strtolower(trim($m[1]));
|
|
196
|
+
$title = $m[3] !== '' ? $m[3] : ($m[4] !== '' ? $m[4] : ($m[5] ?? ''));
|
|
197
|
+
$refDefs[$label] = ['url' => $m[2], 'title' => $title];
|
|
198
|
+
return '';
|
|
199
|
+
},
|
|
200
|
+
$html
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
|
|
140
204
|
// ====================================================================
|
|
141
205
|
// ÉTAPE 3 : BLOCS DE CODE (```lang ... ```)
|
|
142
206
|
// ====================================================================
|
|
@@ -149,8 +213,44 @@ class MD {
|
|
|
149
213
|
return $placeholder;
|
|
150
214
|
}, $html);
|
|
151
215
|
|
|
152
|
-
//
|
|
216
|
+
// ====================================================================
|
|
217
|
+
// ÉTAPE 3a : BLOCS DE CODE INDENTÉS (4 espaces ou 1 tabulation)
|
|
218
|
+
// Reconnu seulement quand précédé d'une ligne vide (ou du début du
|
|
219
|
+
// document) et suivi d'une ligne vide (ou de la fin du document), afin
|
|
220
|
+
// d'éviter les conflits avec l'indentation des listes imbriquées.
|
|
221
|
+
// ====================================================================
|
|
222
|
+
$html = preg_replace_callback(
|
|
223
|
+
'/(?<=\n\n|^)((?:[ ]{4}|\t)[^\n]*(?:\n(?:[ ]{4}|\t)[^\n]*)*)(?=\n\n|\n*$)/',
|
|
224
|
+
function ($matches) use (&$codeBlocks) {
|
|
225
|
+
$lines = explode("\n", $matches[1]);
|
|
226
|
+
$stripped = array_map(static function (string $l): string {
|
|
227
|
+
return preg_replace('/^(?:[ ]{4}|\t)/', '', $l);
|
|
228
|
+
}, $lines);
|
|
229
|
+
$code = htmlspecialchars(implode("\n", $stripped), ENT_QUOTES, 'UTF-8');
|
|
230
|
+
$placeholder = "\x02CB" . count($codeBlocks) . "\x03";
|
|
231
|
+
$codeBlocks[$placeholder] = "<pre><code>{$code}</code></pre>";
|
|
232
|
+
return $placeholder;
|
|
233
|
+
},
|
|
234
|
+
$html
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
// Code inline avec double backticks (permet d'inclure un backtick littéral)
|
|
153
238
|
$inlineCodes = [];
|
|
239
|
+
$html = preg_replace_callback('/``(.+?)``/s', function ($matches) use (&$inlineCodes) {
|
|
240
|
+
$content = $matches[1];
|
|
241
|
+
// Convention standard : si le contenu commence et finit par un
|
|
242
|
+
// espace (et n'est pas uniquement des espaces), on retire un
|
|
243
|
+
// espace de chaque côté — utile pour englober un ` en bordure.
|
|
244
|
+
if (preg_match('/^ (.*[^ ]) $/s', $content, $trim)) {
|
|
245
|
+
$content = $trim[1];
|
|
246
|
+
}
|
|
247
|
+
$code = htmlspecialchars($content, ENT_QUOTES, 'UTF-8');
|
|
248
|
+
$placeholder = "\x02IC" . count($inlineCodes) . "\x03";
|
|
249
|
+
$inlineCodes[$placeholder] = "<code>{$code}</code>";
|
|
250
|
+
return $placeholder;
|
|
251
|
+
}, $html);
|
|
252
|
+
|
|
253
|
+
// Code inline (`...`)
|
|
154
254
|
$html = preg_replace_callback('/`([^`\n]+)`/', function ($matches) use (&$inlineCodes) {
|
|
155
255
|
$code = htmlspecialchars($matches[1], ENT_QUOTES, 'UTF-8');
|
|
156
256
|
$placeholder = "\x02IC" . count($inlineCodes) . "\x03";
|
|
@@ -160,7 +260,45 @@ class MD {
|
|
|
160
260
|
|
|
161
261
|
|
|
162
262
|
// ====================================================================
|
|
163
|
-
// ÉTAPE 3b :
|
|
263
|
+
// ÉTAPE 3b : ÉCHAPPEMENT DES CARACTÈRES (\* \_ \# etc.)
|
|
264
|
+
// Traité après l'extraction du code (le code reste littéral) et avant
|
|
265
|
+
// tout le reste, pour que \* n'ouvre pas une emphase, \# ne crée pas
|
|
266
|
+
// un titre, \- ne crée pas de liste, etc.
|
|
267
|
+
// ====================================================================
|
|
268
|
+
$escapes = [];
|
|
269
|
+
$html = preg_replace_callback(
|
|
270
|
+
'/\\\\([\\\\`*_{}\[\]<>()#+\-.!|])/',
|
|
271
|
+
function ($m) use (&$escapes): string {
|
|
272
|
+
$placeholder = "\x02ESC" . count($escapes) . "\x03";
|
|
273
|
+
$escapes[$placeholder] = htmlspecialchars($m[1], ENT_QUOTES, 'UTF-8');
|
|
274
|
+
return $placeholder;
|
|
275
|
+
},
|
|
276
|
+
$html
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
// ====================================================================
|
|
281
|
+
// ÉTAPE 3d : LIENS AUTOMATIQUES <https://...> et <email@example.com>
|
|
282
|
+
// Traités avant l'encodage XSS car les caractères < > seraient encodés
|
|
283
|
+
// en < > et la regex ne matcherait plus.
|
|
284
|
+
// ====================================================================
|
|
285
|
+
$autolinks = [];
|
|
286
|
+
$html = preg_replace_callback('/<(https?:\/\/[^\s<>]+)>/', function ($m) use (&$autolinks): string {
|
|
287
|
+
$url = htmlspecialchars($m[1], ENT_QUOTES, 'UTF-8');
|
|
288
|
+
$placeholder = "\x02AL" . count($autolinks) . "\x03";
|
|
289
|
+
$autolinks[$placeholder] = "<a href=\"{$url}\" target=\"_blank\" rel=\"noopener noreferrer\">{$url}</a>";
|
|
290
|
+
return $placeholder;
|
|
291
|
+
}, $html);
|
|
292
|
+
$html = preg_replace_callback('/<([^\s<>]+@[^\s<>]+\.[^\s<>]+)>/', function ($m) use (&$autolinks): string {
|
|
293
|
+
$email = htmlspecialchars($m[1], ENT_QUOTES, 'UTF-8');
|
|
294
|
+
$placeholder = "\x02AL" . count($autolinks) . "\x03";
|
|
295
|
+
$autolinks[$placeholder] = "<a href=\"mailto:{$email}\">{$email}</a>";
|
|
296
|
+
return $placeholder;
|
|
297
|
+
}, $html);
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
// ====================================================================
|
|
301
|
+
// ÉTAPE 3e : ALERTES GFM ET BLOCKQUOTES
|
|
164
302
|
// Traités avant l'encodage XSS car le caractère > serait encodé en >
|
|
165
303
|
// et les regex ne matcheraient plus.
|
|
166
304
|
// ====================================================================
|
|
@@ -179,19 +317,26 @@ class MD {
|
|
|
179
317
|
$blockquotes[$placeholder] = "<div class=\"markdown-alert markdown-alert-{$type}\">"
|
|
180
318
|
. "<p class=\"markdown-alert-title\">{$label}</p>"
|
|
181
319
|
. "<p>{$content}</p></div>";
|
|
182
|
-
|
|
320
|
+
// Le \n final consommé par la regex est réinjecté après le
|
|
321
|
+
// placeholder pour ne pas fusionner la ligne vide suivante
|
|
322
|
+
// avec celle du placeholder (ce qui fausserait par exemple
|
|
323
|
+
// la détection d'un titre Setext juste après).
|
|
324
|
+
return $placeholder . (str_ends_with($matches[1], "\n") ? "\n" : '');
|
|
183
325
|
},
|
|
184
326
|
$html
|
|
185
327
|
);
|
|
186
328
|
|
|
187
|
-
// Blockquotes standards
|
|
329
|
+
// Blockquotes standards (imbrication gérée par récursion sur toHtml,
|
|
330
|
+
// qui ré-applique cette même règle sur le contenu déjà dé-préfixé
|
|
331
|
+
// d'un niveau de ">")
|
|
188
332
|
$html = preg_replace_callback('/^((?:>[ \t]?[^\n]*\n?)+)/m', function ($matches) use (&$blockquotes): string {
|
|
189
333
|
$content = preg_replace('/^>[ \t]?/m', '', $matches[1]);
|
|
190
334
|
// Les deux espaces trailing sont laissés tels quels : toHtml() les gère lui-même
|
|
191
335
|
$inner = self::toHtml(trim($content));
|
|
192
336
|
$placeholder = "\x02BQ" . count($blockquotes) . "\x03";
|
|
193
337
|
$blockquotes[$placeholder] = "<blockquote>{$inner}</blockquote>";
|
|
194
|
-
|
|
338
|
+
// Voir commentaire ci-dessus : on préserve le \n final consommé.
|
|
339
|
+
return $placeholder . (str_ends_with($matches[1], "\n") ? "\n" : '');
|
|
195
340
|
}, $html);
|
|
196
341
|
|
|
197
342
|
|
|
@@ -251,7 +396,7 @@ class MD {
|
|
|
251
396
|
|
|
252
397
|
|
|
253
398
|
// ====================================================================
|
|
254
|
-
// ÉTAPE 6 : (Alertes GFM et blockquotes traités à l'étape
|
|
399
|
+
// ÉTAPE 6 : (Alertes GFM et blockquotes traités à l'étape 3e)
|
|
255
400
|
// ====================================================================
|
|
256
401
|
|
|
257
402
|
|
|
@@ -262,45 +407,76 @@ class MD {
|
|
|
262
407
|
$html = preg_replace('/^[ \t]*[-*+] \[[xX]\] (.+)$/m', '<li class="task-item"><input type="checkbox" checked disabled /> $1</li>', $html);
|
|
263
408
|
|
|
264
409
|
|
|
410
|
+
// ====================================================================
|
|
411
|
+
// ÉTAPE 7b : TITRES SETEXT (syntaxe alternative == / --)
|
|
412
|
+
// Titre
|
|
413
|
+
// ===== → <h1>
|
|
414
|
+
//
|
|
415
|
+
// Titre
|
|
416
|
+
// ----- → <h2>
|
|
417
|
+
// Traité avant les titres ATX et avant les lignes séparatrices (une
|
|
418
|
+
// ligne de tirets juste après une ligne de texte est un titre, pas un <hr>).
|
|
419
|
+
// ====================================================================
|
|
420
|
+
$html = preg_replace_callback(
|
|
421
|
+
'/^(?![ \t]*(?:#{1,6}[ \t]|>|```|\||[-*+][ \t]|\d+\.[ \t]))[ \t]*(\S.*?)[ \t]*\n[ \t]*=+[ \t]*$/m',
|
|
422
|
+
function ($matches) {
|
|
423
|
+
$text = trim($matches[1]);
|
|
424
|
+
$id = self::slugify($text);
|
|
425
|
+
return "<h1 id=\"{$id}\">{$text}</h1>";
|
|
426
|
+
},
|
|
427
|
+
$html
|
|
428
|
+
);
|
|
429
|
+
$html = preg_replace_callback(
|
|
430
|
+
'/^(?![ \t]*(?:#{1,6}[ \t]|>|```|\||[-*+][ \t]|\d+\.[ \t]))[ \t]*(\S.*?)[ \t]*\n[ \t]*-+[ \t]*$/m',
|
|
431
|
+
function ($matches) {
|
|
432
|
+
$text = trim($matches[1]);
|
|
433
|
+
$id = self::slugify($text);
|
|
434
|
+
return "<h2 id=\"{$id}\">{$text}</h2>";
|
|
435
|
+
},
|
|
436
|
+
$html
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
|
|
265
440
|
// ====================================================================
|
|
266
441
|
// ÉTAPE 8 : TITRES (ATX : # à ######)
|
|
267
442
|
// ====================================================================
|
|
268
443
|
$html = preg_replace_callback('/^(#{1,6})[ \t]+(.+?)(?:[ \t]+#+)?$/m', function ($matches) {
|
|
269
444
|
$level = strlen($matches[1]);
|
|
270
445
|
$text = trim($matches[2]);
|
|
271
|
-
$id =
|
|
272
|
-
$id = preg_replace('/\s+/', '-', trim($id));
|
|
446
|
+
$id = self::slugify($text);
|
|
273
447
|
return "<h{$level} id=\"{$id}\">{$text}</h{$level}>";
|
|
274
448
|
}, $html);
|
|
275
449
|
|
|
276
450
|
|
|
277
451
|
// ====================================================================
|
|
278
|
-
// ÉTAPE 9 : LISTES (puces et ordonnées)
|
|
452
|
+
// ÉTAPE 9 : LISTES (puces et ordonnées, avec imbrication)
|
|
453
|
+
// Une seule passe détecte un bloc contigu de lignes qui sont soit une
|
|
454
|
+
// puce (-,*,+) soit un item numéroté, quel que soit leur niveau
|
|
455
|
+
// d'indentation ; le bloc est ensuite reconstruit récursivement en
|
|
456
|
+
// <ol>/<ul> imbriqués selon la profondeur d'indentation relative.
|
|
457
|
+
// Les items de tâches (déjà convertis en <li class="task-item">) ne
|
|
458
|
+
// matchent plus ce motif et ne sont donc pas ré-englobés ici.
|
|
279
459
|
// ====================================================================
|
|
280
460
|
$html = preg_replace_callback(
|
|
281
|
-
'/^([ \t]
|
|
282
|
-
function ($matches) {
|
|
283
|
-
$items = preg_split('/\n/', trim($matches[1]));
|
|
284
|
-
$out = "<ol>\n";
|
|
285
|
-
foreach ($items as $item) {
|
|
286
|
-
$text = preg_replace('/^[ \t]*\d+\. /', '', $item);
|
|
287
|
-
$out .= " <li>{$text}</li>\n";
|
|
288
|
-
}
|
|
289
|
-
return $out . "</ol>";
|
|
290
|
-
},
|
|
291
|
-
$html
|
|
292
|
-
);
|
|
293
|
-
|
|
294
|
-
$html = preg_replace_callback(
|
|
295
|
-
'/^([ \t]*[-*+] (?!\[[ xX]\] ).+(?:\n[ \t]*[-*+] (?!\[[ xX]\] ).+)*)/m',
|
|
461
|
+
'/^([ \t]*(?:\d+\.|[-*+])[ \t]+.+(?:\n[ \t]*(?:\d+\.|[-*+])[ \t]+.+)*)/m',
|
|
296
462
|
function ($matches) {
|
|
297
|
-
$
|
|
298
|
-
$
|
|
299
|
-
foreach ($
|
|
300
|
-
|
|
301
|
-
|
|
463
|
+
$lines = explode("\n", $matches[1]);
|
|
464
|
+
$items = [];
|
|
465
|
+
foreach ($lines as $line) {
|
|
466
|
+
if (preg_match('/^([ \t]*)(\d+)\.[ \t]+(.*)$/', $line, $m)) {
|
|
467
|
+
$items[] = ['indent' => self::indentWidth($m[1]), 'type' => 'ol', 'text' => $m[3]];
|
|
468
|
+
} elseif (preg_match('/^([ \t]*)[-*+][ \t]+(.*)$/', $line, $m)) {
|
|
469
|
+
$items[] = ['indent' => self::indentWidth($m[1]), 'type' => 'ul', 'text' => $m[2]];
|
|
470
|
+
}
|
|
302
471
|
}
|
|
303
|
-
|
|
472
|
+
if (empty($items)) return $matches[1];
|
|
473
|
+
// Normalise le niveau d'indentation le plus bas à 0
|
|
474
|
+
$minIndent = min(array_column($items, 'indent'));
|
|
475
|
+
foreach ($items as &$it) $it['indent'] -= $minIndent;
|
|
476
|
+
unset($it);
|
|
477
|
+
|
|
478
|
+
$i = 0;
|
|
479
|
+
return self::buildListTree($items, $i, count($items));
|
|
304
480
|
},
|
|
305
481
|
$html
|
|
306
482
|
);
|
|
@@ -339,32 +515,33 @@ class MD {
|
|
|
339
515
|
$html
|
|
340
516
|
);
|
|
341
517
|
|
|
342
|
-
|
|
518
|
+
$buildLink = static function (string $text, string $href, string $title): string {
|
|
519
|
+
$titleAttr = $title !== '' ? ' title="' . $title . '"' : '';
|
|
520
|
+
$extern = preg_match('/^https?:\/\//i', $href)
|
|
521
|
+
? ' target="_blank" rel="noopener noreferrer"'
|
|
522
|
+
: '';
|
|
523
|
+
return "<a href=\"{$href}\"{$titleAttr}{$extern}>{$text}</a>";
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
// Liens par référence [texte][label] et [texte][] (raccourci = label = texte)
|
|
343
527
|
$html = preg_replace_callback(
|
|
344
|
-
'/\[([^\]]+)\]\
|
|
345
|
-
function ($m): string {
|
|
346
|
-
$text
|
|
347
|
-
$
|
|
348
|
-
|
|
349
|
-
$
|
|
350
|
-
|
|
351
|
-
: '';
|
|
352
|
-
return "<a href=\"{$href}\"{$title}{$extern}>{$text}</a>";
|
|
528
|
+
'/\[([^\]]+)\]\[([^\]]*)\]/',
|
|
529
|
+
function ($m) use (&$refDefs, $buildLink): string {
|
|
530
|
+
$text = $m[1];
|
|
531
|
+
$label = strtolower(trim($m[2] !== '' ? $m[2] : $m[1]));
|
|
532
|
+
if (!isset($refDefs[$label])) return $m[0];
|
|
533
|
+
$def = $refDefs[$label];
|
|
534
|
+
return $buildLink($text, $def['url'], $def['title']);
|
|
353
535
|
},
|
|
354
536
|
$html
|
|
355
537
|
);
|
|
356
538
|
|
|
357
|
-
// Liens
|
|
358
|
-
$html =
|
|
359
|
-
'
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
// URL nues https://...
|
|
365
|
-
$html = preg_replace(
|
|
366
|
-
'/(?<!["\'=>])\b(https?:\/\/[^\s<>"\')\]]+)/',
|
|
367
|
-
'<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>',
|
|
539
|
+
// Liens markdown [texte](url "titre optionnel")
|
|
540
|
+
$html = preg_replace_callback(
|
|
541
|
+
'/\[([^\]]+)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/',
|
|
542
|
+
function ($m) use ($buildLink): string {
|
|
543
|
+
return $buildLink($m[1], $m[2], $m[3] ?? '');
|
|
544
|
+
},
|
|
368
545
|
$html
|
|
369
546
|
);
|
|
370
547
|
|
|
@@ -384,12 +561,15 @@ class MD {
|
|
|
384
561
|
// ====================================================================
|
|
385
562
|
$blockStartTags = ['<h', '<pre', '<ul', '<ol', '<li', '<table', '<thead', '<tbody',
|
|
386
563
|
'<tr', '<td', '<th', '<blockquote', '<div', '<hr', '<img',
|
|
387
|
-
|
|
388
|
-
"\x02CB", "\x02IC", "\x02PLG", "\x02BQ"];
|
|
564
|
+
"\x02CB", "\x02PLG", "\x02BQ"];
|
|
389
565
|
|
|
390
566
|
$isBlockLine = static function (string $line) use ($blockStartTags): bool {
|
|
391
567
|
$t = ltrim($line);
|
|
392
568
|
if ($t === '') return false;
|
|
569
|
+
// Toute balise fermante (</...>) est toujours considérée comme une
|
|
570
|
+
// ligne "bloc" : ça évite qu'une fermeture de <table>, <thead>,
|
|
571
|
+
// <tr>, etc. finisse absorbée dans un <p> environnant.
|
|
572
|
+
if (str_starts_with($t, '</')) return true;
|
|
393
573
|
foreach ($blockStartTags as $tag) {
|
|
394
574
|
if (str_starts_with($t, $tag)) return true;
|
|
395
575
|
}
|
|
@@ -437,8 +617,11 @@ class MD {
|
|
|
437
617
|
$html = strtr($html, $blockquotes);
|
|
438
618
|
$html = strtr($html, $codeBlocks);
|
|
439
619
|
$html = strtr($html, $inlineCodes);
|
|
620
|
+
$html = strtr($html, $autolinks);
|
|
621
|
+
// Les échappements sont réinjectés en tout dernier, une fois que plus
|
|
622
|
+
// aucune regex Markdown ne peut les interpréter.
|
|
623
|
+
$html = strtr($html, $escapes);
|
|
440
624
|
|
|
441
625
|
return $html;
|
|
442
626
|
}
|
|
443
|
-
}
|
|
444
|
-
|
|
627
|
+
}
|
|
@@ -54,6 +54,137 @@ class YAML
|
|
|
54
54
|
return self::parse(file_get_contents($path), $assoc);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Charge un fichier YAML ou JSON, puis parcourt récursivement le résultat
|
|
59
|
+
* et remplace toute valeur string qui correspond à un chemin relatif vers
|
|
60
|
+
* un fichier YAML/JSON existant par le contenu désérialisé de ce fichier.
|
|
61
|
+
*
|
|
62
|
+
* Chaque fichier inclus est lui-même résolu relativement à son propre
|
|
63
|
+
* répertoire, et ainsi de suite (récursif).
|
|
64
|
+
*
|
|
65
|
+
* Si la chaîne ne se termine pas par .yml/.yaml/.json, ou si le fichier
|
|
66
|
+
* résolu n'existe pas, la valeur est conservée telle quelle.
|
|
67
|
+
*
|
|
68
|
+
* Les références circulaires (ex. A → B → A) lèvent une RuntimeException.
|
|
69
|
+
*
|
|
70
|
+
* Utilisation :
|
|
71
|
+
* $data = YAML::loadFile('/chemin/vers/config.yaml');
|
|
72
|
+
* $data = YAML::loadFile('/chemin/vers/config.yaml', true); // arrays assoc
|
|
73
|
+
*
|
|
74
|
+
* @param string $path Chemin vers le fichier racine (YAML ou JSON).
|
|
75
|
+
* @param bool $assoc true → mappings en array, false → stdClass.
|
|
76
|
+
* @return mixed
|
|
77
|
+
*/
|
|
78
|
+
public static function loadFile(string $path, bool $assoc = false): mixed
|
|
79
|
+
{
|
|
80
|
+
$absolute = realpath($path);
|
|
81
|
+
if ($absolute === false || !is_readable($absolute)) {
|
|
82
|
+
throw new \RuntimeException("Impossible de lire le fichier : $path");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return self::loadFileRecursive($absolute, $assoc, []);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// -------------------------------------------------------------------------
|
|
89
|
+
// Méthodes privées pour loadFile
|
|
90
|
+
// -------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Charge et résout un fichier, en propageant la liste des ancêtres pour
|
|
94
|
+
* détecter les cycles.
|
|
95
|
+
*
|
|
96
|
+
* @param string $absolute Chemin absolu canonique du fichier à charger.
|
|
97
|
+
* @param bool $assoc
|
|
98
|
+
* @param string[] $ancestors Chemins absolus des fichiers en cours de traitement.
|
|
99
|
+
* @return mixed
|
|
100
|
+
*/
|
|
101
|
+
private static function loadFileRecursive(string $absolute, bool $assoc, array $ancestors): mixed
|
|
102
|
+
{
|
|
103
|
+
if (in_array($absolute, $ancestors, true)) {
|
|
104
|
+
throw new \RuntimeException(
|
|
105
|
+
"Référence circulaire détectée : " . implode(' → ', $ancestors) . " → $absolute"
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
$ext = strtolower(pathinfo($absolute, PATHINFO_EXTENSION));
|
|
110
|
+
$raw = file_get_contents($absolute);
|
|
111
|
+
$dir = dirname($absolute);
|
|
112
|
+
|
|
113
|
+
if ($ext === 'json') {
|
|
114
|
+
$data = json_decode($raw, $assoc, 512, JSON_THROW_ON_ERROR);
|
|
115
|
+
} else {
|
|
116
|
+
// .yml, .yaml ou autre extension traitée comme YAML
|
|
117
|
+
$data = self::parse($raw, $assoc);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return self::resolveNode($data, $dir, $assoc, [...$ancestors, $absolute]);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Parcourt récursivement une valeur PHP (objet stdClass, array, string,
|
|
125
|
+
* scalaire) et résout les références vers des fichiers externes.
|
|
126
|
+
*
|
|
127
|
+
* @param mixed $node
|
|
128
|
+
* @param string $dir Répertoire du fichier qui contient ce nœud.
|
|
129
|
+
* @param bool $assoc
|
|
130
|
+
* @param string[] $ancestors
|
|
131
|
+
* @return mixed
|
|
132
|
+
*/
|
|
133
|
+
private static function resolveNode(mixed $node, string $dir, bool $assoc, array $ancestors): mixed
|
|
134
|
+
{
|
|
135
|
+
if (is_string($node)) {
|
|
136
|
+
return self::resolveString($node, $dir, $assoc, $ancestors);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (is_array($node)) {
|
|
140
|
+
foreach ($node as $key => $value) {
|
|
141
|
+
$node[$key] = self::resolveNode($value, $dir, $assoc, $ancestors);
|
|
142
|
+
}
|
|
143
|
+
return $node;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if ($node instanceof \stdClass) {
|
|
147
|
+
foreach ($node as $key => $value) {
|
|
148
|
+
$node->$key = self::resolveNode($value, $dir, $assoc, $ancestors);
|
|
149
|
+
}
|
|
150
|
+
return $node;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// int, float, bool, null → retourné tel quel
|
|
154
|
+
return $node;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Si la chaîne pointe vers un fichier YAML/JSON existant (chemin relatif
|
|
159
|
+
* au répertoire $dir), charge ce fichier récursivement. Sinon retourne la
|
|
160
|
+
* chaîne d'origine.
|
|
161
|
+
*
|
|
162
|
+
* @param string $str
|
|
163
|
+
* @param string $dir
|
|
164
|
+
* @param bool $assoc
|
|
165
|
+
* @param string[] $ancestors
|
|
166
|
+
* @return mixed
|
|
167
|
+
*/
|
|
168
|
+
private static function resolveString(string $str, string $dir, bool $assoc, array $ancestors): mixed
|
|
169
|
+
{
|
|
170
|
+
$trimmed = trim($str);
|
|
171
|
+
|
|
172
|
+
// Filtre rapide sur l'extension
|
|
173
|
+
if (!preg_match('/\.(ya?ml|json)$/i', $trimmed)) {
|
|
174
|
+
return $str;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Résolution du chemin relatif au répertoire du fichier parent
|
|
178
|
+
$candidate = $dir . DIRECTORY_SEPARATOR . $trimmed;
|
|
179
|
+
$absolute = realpath($candidate);
|
|
180
|
+
|
|
181
|
+
if ($absolute === false || !is_readable($absolute)) {
|
|
182
|
+
return $str; // Fichier introuvable → string ordinaire
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return self::loadFileRecursive($absolute, $assoc, $ancestors);
|
|
186
|
+
}
|
|
187
|
+
|
|
57
188
|
// -------------------------------------------------------------------------
|
|
58
189
|
// Parsing récursif
|
|
59
190
|
// -------------------------------------------------------------------------
|
package/src/prepros.js
CHANGED
|
@@ -1,16 +1,48 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path, { dirname } from "path";
|
|
3
|
+
import isBinary from './utils/isbinary.js';
|
|
4
|
+
import joinWith from './utils/joinwith.js'
|
|
3
5
|
import { spawn } from 'child_process';
|
|
4
6
|
import { fileURLToPath } from 'url';
|
|
7
|
+
import { walkFile } from '@kirigami/struct-walker';
|
|
5
8
|
import { getPHPRuntime, getPHPRuntimeWithNetwork } from "@kirigami/php-wasm";
|
|
6
|
-
import isBinary from './utils/isbinary.js';
|
|
7
|
-
import joinWith from './utils/joinwith.js'
|
|
8
|
-
import yaml from "js-yaml";
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
const __project = process.cwd();
|
|
12
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
13
|
const __configpath = path.join(__project, 'kirigami.yaml');
|
|
14
|
+
let __root = null;
|
|
15
|
+
let __php = null;
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if (!fs.existsSync(__configpath)) throw `Config file not found: ${__configpath}`;
|
|
19
|
+
const config = await walkFile(__configpath);
|
|
20
|
+
if(!config) throw `Invalid config file: ${__configpath}`;
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
const getPHPInstance = async () => {
|
|
24
|
+
if(!__php) {
|
|
25
|
+
if(config?.kirigami?.root === undefined) throw `Missing prepros:root property in config file: ${__configpath}`;
|
|
26
|
+
__root = path.join(__project, config.kirigami.root);
|
|
27
|
+
if (!fs.existsSync(__root)) throw `Invalid prepros:root path: ${__root}`;
|
|
28
|
+
|
|
29
|
+
const preprosConfig = config.prepros;
|
|
30
|
+
preprosConfig.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
31
|
+
preprosConfig.root = joinWith('/project/', config?.kirigami?.root);
|
|
32
|
+
preprosConfig.data = config.kirigami || {};
|
|
33
|
+
|
|
34
|
+
__php = await (preprosConfig.network ? getPHPRuntimeWithNetwork() : getPHPRuntime());
|
|
35
|
+
__php.setSpawnHandler((command, args, options) => spawn(command, args, options));
|
|
36
|
+
__php.preprosConfig = preprosConfig;
|
|
37
|
+
|
|
38
|
+
const mountPaths = [];
|
|
39
|
+
const __cache = path.join(__project, '.cache.db');
|
|
40
|
+
mountPath(__php, __dirname, '/prepros');
|
|
41
|
+
mountPath(__php, joinWith(__project, config?.kirigami?.root), joinWith('/project', config?.kirigami?.root));
|
|
42
|
+
if (fs.existsSync(__cache)) mountPath(__php, __cache, '/project/.cache.db');
|
|
43
|
+
}
|
|
44
|
+
return __php;
|
|
45
|
+
}
|
|
14
46
|
|
|
15
47
|
|
|
16
48
|
const mountPath = (php, localPath, virtualDir) => {
|
|
@@ -32,14 +64,16 @@ const mountPath = (php, localPath, virtualDir) => {
|
|
|
32
64
|
|
|
33
65
|
|
|
34
66
|
const run = async (args) => {
|
|
35
|
-
php
|
|
67
|
+
const php = await getPHPInstance();
|
|
36
68
|
const output = await php.runStream({
|
|
37
69
|
scriptPath: '/prepros/prepros.php',
|
|
38
|
-
env: {
|
|
70
|
+
env: {
|
|
71
|
+
PREPROS_ARGS: JSON.stringify(args),
|
|
72
|
+
PREPROS_CONFIG: JSON.stringify(php.preprosConfig)
|
|
73
|
+
}
|
|
39
74
|
});
|
|
40
75
|
const stdout = await output.stdoutText;
|
|
41
76
|
const stderr = await output.stderrText;
|
|
42
|
-
// console.log(stdout);
|
|
43
77
|
let retobj;
|
|
44
78
|
try {
|
|
45
79
|
retobj = JSON.parse(stdout);
|
|
@@ -64,44 +98,29 @@ const run = async (args) => {
|
|
|
64
98
|
|
|
65
99
|
|
|
66
100
|
const render = async (file = '.') => {
|
|
101
|
+
const php = await getPHPInstance();
|
|
67
102
|
const target = path.resolve(__project, config?.kirigami?.root, file);
|
|
68
103
|
const fsvm = path.join('/project', config?.kirigami?.root, file).replace(/\\/g, '/');
|
|
69
104
|
mountPath(php, target, fsvm);
|
|
105
|
+
if(config?.prepros?.before) {
|
|
106
|
+
const include = path.resolve(__project, config?.kirigami?.root, config?.prepros?.before);
|
|
107
|
+
const dest = path.join('/project', config?.kirigami?.root, config?.prepros?.before).replace(/\\/g, '/');
|
|
108
|
+
mountPath(php, include, dest);
|
|
109
|
+
}
|
|
110
|
+
if(config?.prepros?.after) {
|
|
111
|
+
const include = path.resolve(__project, config?.kirigami?.root, config?.prepros?.after);
|
|
112
|
+
const dest = path.join('/project', config?.kirigami?.root, config?.prepros?.after).replace(/\\/g, '/');
|
|
113
|
+
mountPath(php, include, dest);
|
|
114
|
+
}
|
|
70
115
|
return run([fsvm]);
|
|
71
116
|
}
|
|
72
117
|
|
|
73
118
|
|
|
74
119
|
const sitemap = async (dir) => {
|
|
120
|
+
const php = await getPHPInstance();
|
|
75
121
|
mountPath(php, __root, '/project/' + config?.kirigami?.root);
|
|
76
122
|
return run(['sitemap']);
|
|
77
123
|
}
|
|
78
124
|
|
|
79
125
|
|
|
80
|
-
if (!fs.existsSync(__configpath)) throw `Config file not found: ${__configpath}`;
|
|
81
|
-
const configContents = fs.readFileSync(__configpath, 'utf8');
|
|
82
|
-
const config = yaml.load(configContents);
|
|
83
|
-
if(!config) throw `Invalid config file: ${__configpath}`;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if(config?.kirigami?.root === undefined) throw `Missing prepros:root property in config file: ${__configpath}`;
|
|
87
|
-
const __root = path.join(__project, config.kirigami.root);
|
|
88
|
-
if (!fs.existsSync(__root)) throw `Invalid prepros:root path: ${__root}`;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const preprosConfig = config.prepros;
|
|
92
|
-
preprosConfig.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
93
|
-
preprosConfig.root = joinWith('/project/', config?.kirigami?.root);
|
|
94
|
-
preprosConfig.data = config.kirigami || {};
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const php = await (preprosConfig.network ? getPHPRuntimeWithNetwork() : getPHPRuntime());
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const mountPaths = [];
|
|
101
|
-
const __cache = path.join(__project, '.cache.db');
|
|
102
|
-
mountPath(php, __dirname, '/prepros');
|
|
103
|
-
mountPath(php, joinWith(__project, config?.kirigami?.root), joinWith('/project', config?.kirigami?.root));
|
|
104
|
-
if (fs.existsSync(__cache)) mountPath(php, __cache, '/project/.cache.db');
|
|
105
|
-
|
|
106
|
-
|
|
107
126
|
export { render, sitemap };
|
package/src/prepros.php
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<?php
|
|
2
2
|
|
|
3
|
-
ini_set('display_errors', 1);
|
|
4
3
|
ini_set('log_errors', 1);
|
|
5
|
-
ini_set('error_log', 'php://stderr');
|
|
6
4
|
ini_set('html_errors', 0);
|
|
5
|
+
ini_set('display_errors', 1);
|
|
7
6
|
ini_set('error_reporting', 32767);
|
|
7
|
+
ini_set('error_log', 'php://stderr');
|
|
8
8
|
error_reporting(E_ALL);
|
|
9
9
|
|
|
10
10
|
|