@kirigami/php-prepros 1.1.0 → 1.2.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.
@@ -98,45 +98,45 @@ class SCRAPER
98
98
  }
99
99
  }
100
100
 
101
- if(($results = $xpath->query('//meta[@name="dc.title"]')) && $results->length) $metas->title = getFirstItemAttr($results, 'content');
102
- if(($results = $xpath->query('//meta[@name="dcterms.title"]')) && $results->length) $metas->title = getFirstItemAttr($results, 'content');
103
- if(($results = $xpath->query('//meta[@name="twitter:title"]')) && $results->length) $metas->title = getFirstItemAttr($results, 'content');
104
- if(($results = $xpath->query('//meta[@property="og:title"]')) && $results->length) $metas->title = getFirstItemAttr($results, 'content');
105
- if(($results = $xpath->query('//shreddit-title')) && $results->length) $metas->title = getFirstItemAttr($results, 'title');
106
- if(($results = $xpath->query('//meta[@name="dc.description"]')) && $results->length) $metas->description = getFirstItemAttr($results, 'content');
107
- if(($results = $xpath->query('//meta[@name="dcterms.description"]')) && $results->length) $metas->description = getFirstItemAttr($results, 'content');
108
- if(($results = $xpath->query('//meta[@name="twitter:description"]')) && $results->length) $metas->description = getFirstItemAttr($results, 'content');
109
- if(($results = $xpath->query('//meta[@property="og:description"]')) && $results->length) $metas->description = getFirstItemAttr($results, 'content');
110
- if(($results = $xpath->query('//meta[@name="description"]')) && $results->length) $metas->description = getFirstItemAttr($results, 'content');
111
- if(($results = $xpath->query('//meta[@name="twitter:image"]')) && $results->length) $metas->image = getFirstItemAttr($results, 'content');
112
- if(($results = $xpath->query('//meta[@property="og:image"]')) && $results->length) $metas->image = getFirstItemAttr($results, 'content');
113
- if(($results = $xpath->query('//meta[@name="og:image"]')) && $results->length) $metas->image = getFirstItemAttr($results, 'content');
114
- if(($results = $xpath->query('//img[@rel="og:image rdfs:seeAlso"]')) && $results->length) $metas->image = getFirstItemAttr($results, 'src');
115
- if(($results = $xpath->query('//link[@rel="canonical"]')) && $results->length) $metas->url = getFirstItemAttr($results, 'href');
116
- if(($results = $xpath->query('//meta[@name="twitter:url"]')) && $results->length) $metas->url = getFirstItemAttr($results, 'content');
117
- if(($results = $xpath->query('//meta[@property="og:url"]')) && $results->length) $metas->url = getFirstItemAttr($results, 'content');
118
-
119
- if(empty($metas->label) && ($results = $xpath->query('//meta[@name="twitter:app:name:iphone"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
120
- if(empty($metas->label) && ($results = $xpath->query('//meta[@name="twitter:app:name:googleplay"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
121
- if(empty($metas->label) && ($results = $xpath->query('//meta[@name="og:site_name"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
122
- if(empty($metas->label) && ($results = $xpath->query('//meta[@property="og:site_name"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
123
- if(empty($metas->label) && ($results = $xpath->query('//meta[@name="dc.publisher"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
124
- if(empty($metas->label) && ($results = $xpath->query('//meta[@name="DC.Publisher"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
125
- if(empty($metas->label) && ($results = $xpath->query('//meta[@name="publisher"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
126
- if(empty($metas->label) && ($results = $xpath->query('//meta[@property="article:author"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
127
- if(empty($metas->label) && ($results = $xpath->query('//meta[@name="author"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
128
- if(empty($metas->label) && ($results = $xpath->query('//meta[@name="twitter:creator"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
129
- if(empty($metas->label) && ($results = $xpath->query('//meta[@name="dcterms.creator"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
130
- if(empty($metas->label) && ($results = $xpath->query('//meta[@property="al:ios:app_name"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
131
- if(empty($metas->label) && ($results = $xpath->query('//meta[@property="al:android:app_name"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
132
- if(empty($metas->label) && ($results = $xpath->query('//meta[@name="DC.publisher"]')) && $results->length) $metas->label = getFirstItemAttr($results, 'content');
101
+ if(($results = $xpath->query('//meta[@name="dc.title"]')) && $results->length) $metas->title = self::getFirstItemAttr($results, 'content');
102
+ if(($results = $xpath->query('//meta[@name="dcterms.title"]')) && $results->length) $metas->title = self::getFirstItemAttr($results, 'content');
103
+ if(($results = $xpath->query('//meta[@name="twitter:title"]')) && $results->length) $metas->title = self::getFirstItemAttr($results, 'content');
104
+ if(($results = $xpath->query('//meta[@property="og:title"]')) && $results->length) $metas->title = self::getFirstItemAttr($results, 'content');
105
+ if(($results = $xpath->query('//shreddit-title')) && $results->length) $metas->title = self::getFirstItemAttr($results, 'title');
106
+ if(($results = $xpath->query('//meta[@name="dc.description"]')) && $results->length) $metas->description = self::getFirstItemAttr($results, 'content');
107
+ if(($results = $xpath->query('//meta[@name="dcterms.description"]')) && $results->length) $metas->description = self::getFirstItemAttr($results, 'content');
108
+ if(($results = $xpath->query('//meta[@name="twitter:description"]')) && $results->length) $metas->description = self::getFirstItemAttr($results, 'content');
109
+ if(($results = $xpath->query('//meta[@property="og:description"]')) && $results->length) $metas->description = self::getFirstItemAttr($results, 'content');
110
+ if(($results = $xpath->query('//meta[@name="description"]')) && $results->length) $metas->description = self::getFirstItemAttr($results, 'content');
111
+ if(($results = $xpath->query('//meta[@name="twitter:image"]')) && $results->length) $metas->image = self::getFirstItemAttr($results, 'content');
112
+ if(($results = $xpath->query('//meta[@property="og:image"]')) && $results->length) $metas->image = self::getFirstItemAttr($results, 'content');
113
+ if(($results = $xpath->query('//meta[@name="og:image"]')) && $results->length) $metas->image = self::getFirstItemAttr($results, 'content');
114
+ if(($results = $xpath->query('//img[@rel="og:image rdfs:seeAlso"]')) && $results->length) $metas->image = self::getFirstItemAttr($results, 'src');
115
+ if(($results = $xpath->query('//link[@rel="canonical"]')) && $results->length) $metas->url = self::getFirstItemAttr($results, 'href');
116
+ if(($results = $xpath->query('//meta[@name="twitter:url"]')) && $results->length) $metas->url = self::getFirstItemAttr($results, 'content');
117
+ if(($results = $xpath->query('//meta[@property="og:url"]')) && $results->length) $metas->url = self::getFirstItemAttr($results, 'content');
118
+
119
+ if(empty($metas->label) && ($results = $xpath->query('//meta[@name="twitter:app:name:iphone"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
120
+ if(empty($metas->label) && ($results = $xpath->query('//meta[@name="twitter:app:name:googleplay"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
121
+ if(empty($metas->label) && ($results = $xpath->query('//meta[@name="og:site_name"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
122
+ if(empty($metas->label) && ($results = $xpath->query('//meta[@property="og:site_name"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
123
+ if(empty($metas->label) && ($results = $xpath->query('//meta[@name="dc.publisher"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
124
+ if(empty($metas->label) && ($results = $xpath->query('//meta[@name="DC.Publisher"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
125
+ if(empty($metas->label) && ($results = $xpath->query('//meta[@name="publisher"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
126
+ if(empty($metas->label) && ($results = $xpath->query('//meta[@property="article:author"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
127
+ if(empty($metas->label) && ($results = $xpath->query('//meta[@name="author"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
128
+ if(empty($metas->label) && ($results = $xpath->query('//meta[@name="twitter:creator"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
129
+ if(empty($metas->label) && ($results = $xpath->query('//meta[@name="dcterms.creator"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
130
+ if(empty($metas->label) && ($results = $xpath->query('//meta[@property="al:ios:app_name"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
131
+ if(empty($metas->label) && ($results = $xpath->query('//meta[@property="al:android:app_name"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
132
+ if(empty($metas->label) && ($results = $xpath->query('//meta[@name="DC.publisher"]')) && $results->length) $metas->label = self::getFirstItemAttr($results, 'content');
133
133
  if(empty($metas->label) && ($results = $xpath->query('//span[@class="footer__signature"]')) && $results->length) $metas->label = $results->item(0)->textContent;
134
134
  if(empty($metas->label) && ($results = $xpath->query('//p[@class="site-title"]')) && $results->length) $metas->label = trim($results->item(0)->textContent);
135
- if(empty($metas->label) && ($results = $xpath->query('//meta[@name="copyright"]')) && $results->length) $metas->label = trim(getFirstItemAttr($results, 'content'), '© ');
135
+ if(empty($metas->label) && ($results = $xpath->query('//meta[@name="copyright"]')) && $results->length) $metas->label = trim(self::getFirstItemAttr($results, 'content'), '© ');
136
136
  if(empty($metas->label) && preg_match("#\{'blogId': '[0-9]+', 'title': '([^']+)'#i", $contents, $m)) $metas->label = $m[1];
137
137
 
138
138
  if(($results = $xpath->query('//link[@rel="alternate" and @type="application/json+oembed" and @href]')) && $results->length) {
139
- if(STR::is_url(($oembedurl = getFirstItemAttr($results, 'href')))) {
139
+ if(STR::is_url(($oembedurl = self::getFirstItemAttr($results, 'href')))) {
140
140
  if($json = self::getContents($oembedurl)) {
141
141
  if($oem = json_decode($json)) {
142
142
  if(empty($metas->title) && !empty($oem->title)) $metas->title = $oem->title;
@@ -171,7 +171,7 @@ class SCRAPER
171
171
  }
172
172
  if(empty($metas->label)) {
173
173
  if(($results = $xpath->query('//link[@rel="manifest" and @href]')) && $results->length) {
174
- if($manifesturl = get_absolute_url($url, getFirstItemAttr($results, 'href'))) {
174
+ if($manifesturl = get_absolute_url($url, self::getFirstItemAttr($results, 'href'))) {
175
175
  if($json = self::getContents($manifesturl)) {
176
176
  if($manifest = json_decode($json)) {
177
177
  if(!empty($manifest->name)) $metas->label = $manifest->name;
@@ -181,10 +181,10 @@ class SCRAPER
181
181
  }
182
182
  }
183
183
  if(empty($metas->label) && (($results = $xpath->query('//meta[@property="fb:page_id"]')) && $results->length)) {
184
- if($_contents = self::getContents('https://facebook.com/' . getFirstItemAttr($results, 'content'))) {
184
+ if($_contents = self::getContents('https://facebook.com/' . self::getFirstItemAttr($results, 'content'))) {
185
185
  if($xp = self::loadHTML($_contents)) {
186
186
  if(($results = $xp->query('//meta[@property="og:title"]')) && $results->length) {
187
- $metas->label = getFirstItemAttr($results, 'content');
187
+ $metas->label = self::getFirstItemAttr($results, 'content');
188
188
  }
189
189
  }
190
190
  }
@@ -196,14 +196,14 @@ class SCRAPER
196
196
 
197
197
 
198
198
  if(empty($metas->image) && ($results = $xpath->query('//span[@class="background-image not-mobile"]')) && $results->length)
199
- if(preg_match('#url\(\'(.*?)\'\)#i', getFirstItemAttr($results, 'style'), $m))
199
+ if(preg_match('#url\(\'(.*?)\'\)#i', self::getFirstItemAttr($results, 'style'), $m))
200
200
  $metas->image = $m[1];
201
201
 
202
- if(empty($metas->image) && ($results = $xpath->query('//link[@rel="icon" and @type="image/jpeg"]')) && $results->length) $metas->image = getFirstItemAttr($results, 'href');
203
- if(empty($metas->image) && ($results = $xpath->query('//link[@rel="icon" and @type="image/png"]')) && $results->length) $metas->image = getFirstItemAttr($results, 'href');
204
- if(empty($metas->image) && ($results = $xpath->query('//article//img')) && $results->length) $metas->image = getFirstItemAttr($results, 'src');
205
- if(empty($metas->image) && ($results = $xpath->query('//div[@id="image_box"]//img')) && $results->length) $metas->image = getFirstItemAttr($results, 'src');
206
- if(empty($metas->image) && ($results = $xpath->query('//link[@rel="shortcut icon" and @type="image/jpeg" and @href]')) && $results->length) $metas->image = getFirstItemAttr($results, 'href');
202
+ if(empty($metas->image) && ($results = $xpath->query('//link[@rel="icon" and @type="image/jpeg"]')) && $results->length) $metas->image = self::getFirstItemAttr($results, 'href');
203
+ if(empty($metas->image) && ($results = $xpath->query('//link[@rel="icon" and @type="image/png"]')) && $results->length) $metas->image = self::getFirstItemAttr($results, 'href');
204
+ if(empty($metas->image) && ($results = $xpath->query('//article//img')) && $results->length) $metas->image = self::getFirstItemAttr($results, 'src');
205
+ if(empty($metas->image) && ($results = $xpath->query('//div[@id="image_box"]//img')) && $results->length) $metas->image = self::getFirstItemAttr($results, 'src');
206
+ if(empty($metas->image) && ($results = $xpath->query('//link[@rel="shortcut icon" and @type="image/jpeg" and @href]')) && $results->length) $metas->image = self::getFirstItemAttr($results, 'href');
207
207
  if(empty($metas->image) && ($results = $xpath->query('//link[@rel="apple-touch-icon" and @sizes]')) && $results->length) {
208
208
  $size = 0;
209
209
  foreach($results as $item) {
@@ -215,14 +215,14 @@ class SCRAPER
215
215
  }
216
216
  }
217
217
  }
218
- if(empty($metas->image) && ($results = $xpath->query('//link[@rel="apple-touch-icon"]')) && $results->length) $metas->image = getFirstItemAttr($results, 'href');
218
+ if(empty($metas->image) && ($results = $xpath->query('//link[@rel="apple-touch-icon"]')) && $results->length) $metas->image = self::getFirstItemAttr($results, 'href');
219
219
  if(empty($metas->title) && ($results = $xpath->query('//title')) && $results->length) $metas->title = $results->item(0)->textContent;
220
220
  if(empty($metas->label)) { $parts = explode('|', $metas->title); if(count($parts) > 1) $metas->label = trim(array_pop($parts)); }
221
221
  if(empty($metas->label)) { $parts = explode(' - ', $metas->title); if(count($parts) > 1) $metas->label = trim(array_pop($parts)); }
222
222
  if(strpos($metas->label, '@') === 0) $metas->label = ucwords(preg_replace('#^@#i', '', $metas->label));
223
223
 
224
224
  if(empty($metas->label) && ($results = $xpath->query('//div[@id="elevate-global" and @data-elevate-global]')) && $results->length)
225
- if($data = json_decode(getFirstItemAttr($results, 'data-elevate-global')))
225
+ if($data = json_decode(self::getFirstItemAttr($results, 'data-elevate-global')))
226
226
  if(!empty($data->content_organization))
227
227
  $metas->label = $data->content_organization;
228
228
 
@@ -248,9 +248,9 @@ class SCRAPER
248
248
  if($_contents = self::getContents(str_replace('\\/', '/', $m[1])))
249
249
  if($xp = self::loadHTML($_contents))
250
250
  if(($results = $xp->query('//link[@rel="preload" and @as="image" and contains(@href, ".jpg")]')) && $results->length)
251
- $metas->image = getFirstItemAttr($results, 'href');
251
+ $metas->image = self::getFirstItemAttr($results, 'href');
252
252
 
253
- if(!url_exists($metas->image, '^image/') && ($results = $xpath->query('//img[@src]')) && $results->length) {
253
+ if(!self::url_exists($metas->image, '^image/') && ($results = $xpath->query('//img[@src]')) && $results->length) {
254
254
  foreach($results as $item) {
255
255
  /** @var DOMElement $item */
256
256
  if(in_array(pathinfo(parse_url($item->getAttribute('src'), PHP_URL_PATH), PATHINFO_EXTENSION), ['jpg', 'jpeg', 'png', 'webp'])) {
@@ -301,20 +301,25 @@ class SCRAPER
301
301
  return $metas;
302
302
  }
303
303
 
304
- }
305
304
 
305
+ private static function getFirstItemAttr(\Dom\Node|\Dom\NodeList $results, string $attr) {
306
+ /** @var DOM\Element $item */
307
+ $item = $results->item(0);
308
+ return $item->getAttribute($attr);
309
+ }
310
+
311
+
312
+ private static function url_exists(string $url, string|null $mimereg = null) {
313
+ if(($exists = CACHE::get(($key = 'urlexists_' . STR::shorthash($url)))) !== null) return $exists->response;
314
+ $exists = new stdClass;
315
+ $exists->response = CURL::urlExists($url, $mimereg);
316
+ CACHE::set($key, $exists);
317
+ return $exists->response;
318
+ }
306
319
 
307
- function getFirstItemAttr(\Dom\Node|\Dom\NodeList $results, string $attr) {
308
- /** @var DOM\Element $item */
309
- $item = $results->item(0);
310
- return $item->getAttribute($attr);
311
320
  }
312
321
 
313
322
 
314
- function url_exists(string $url, string|null $mimereg = null) {
315
- if(($exists = CACHE::get(($key = 'urlexists_' . STR::shorthash($url)))) !== null) return $exists->response;
316
- $exists = new stdClass;
317
- $exists->response = CURL::urlExists($url, $mimereg);
318
- CACHE::set($key, $exists);
319
- return $exists->response;
320
- }
323
+
324
+
325
+
@@ -12,21 +12,51 @@ class STR
12
12
 
13
13
  public static function replaceTags(string $tag, string $contents, callable $clb): string
14
14
  {
15
- $contents = preg_replace_callback('#<' . preg_quote($tag, '#') . '([^>]*)>(.*?)</' . preg_quote($tag, '#') . '>#msi', function ($m) use ($clb) {
16
- return call_user_func($clb, $m[0], self::parseHtmlAttributes($m[1]), $m[2]);
17
- }, $contents);
18
- return $contents;
15
+ $t = preg_quote($tag, '#');
16
+
17
+ $pattern = '#<' . $t . '([^>]*)>(.*?)</' . $t . '>'
18
+ . '|<' . $t . '([^>]*)/>'
19
+ . '|<' . $t . '([^>]*)>#msi';
20
+
21
+ return preg_replace_callback($pattern, function ($m) use ($clb) {
22
+ if (isset($m[1]) || isset($m[2])) {
23
+ // paired form: <tag>content</tag>
24
+ $attrs = $m[1] ?? '';
25
+ $inner = $m[2] ?? '';
26
+ } elseif (isset($m[3])) {
27
+ // self-closing: <tag ... />
28
+ $attrs = $m[3];
29
+ $inner = '';
30
+ } else {
31
+ // opening only, no closing (img, meta, br, ...)
32
+ $attrs = $m[4] ?? '';
33
+ $inner = '';
34
+ }
35
+ return call_user_func($clb, $m[0], self::parseHtmlAttributes($attrs), $inner);
36
+ }, $contents, -1, $count, PREG_UNMATCHED_AS_NULL); // <-- the flag that fixes everything
19
37
  }
20
38
 
21
39
 
22
40
  public static function parseHtmlAttributes(string $attributes): array
23
41
  {
24
- if (preg_match_all('#(\\w+)\s*=\\s*("[^"]*"|\'[^\']*\'|[^"\'\\s>]*)#i', $attributes, $m)) {
25
- foreach ($m[1] as $k => $key) {
26
- $attrs[strtolower($key)] = stripslashes(substr($m[2][$k], 1, -1));;
42
+ $attrs = [];
43
+ if (preg_match_all('#(\w+)(?:\s*=\s*("[^"]*"|\'[^\']*\'|[^"\'\s>]*))?#i', $attributes, $m, PREG_SET_ORDER)) {
44
+ foreach ($m as $match) {
45
+ $key = strtolower($match[1]);
46
+ if (isset($match[2]) && $match[2] !== '') {
47
+ $value = $match[2];
48
+ // strip the quotes only if they're present
49
+ if (($value[0] === '"' || $value[0] === "'") && $value[0] === substr($value, -1)) {
50
+ $value = substr($value, 1, -1);
51
+ }
52
+ $attrs[$key] = stripslashes($value);
53
+ } else {
54
+ // boolean attribute: selected, muted, disabled, checked...
55
+ $attrs[$key] = true;
56
+ }
27
57
  }
28
58
  }
29
- return isset($attrs) ? $attrs : [];
59
+ return $attrs;
30
60
  }
31
61
 
32
62
 
@@ -55,26 +85,30 @@ class STR
55
85
 
56
86
  public static function html_entities_decode(string $str): string
57
87
  {
58
- // $str = html_entity_decode(trim($str), ENT_QUOTES, 'UTF-8');
59
- // $str = htmlentities($str, ENT_QUOTES, 'UTF-8');
60
- // $str = mb_convert_encoding($str, 'HTML-ENTITIES', 'UTF-8');
61
88
  $str = html_entity_decode(trim($str), ENT_QUOTES, 'UTF-8');
62
- // $str = htmlentities($str, ENT_QUOTES, 'UTF-8');
63
89
  return $str;
64
90
  }
65
91
 
66
92
 
67
- public static function shorthash(string$str): string
93
+ public static function shorthash(string $str): string
68
94
  {
69
95
  return substr(hash('sha256', $str), 0, 12);
70
96
  }
71
97
 
72
98
 
73
- public static function slug(string $str): string
99
+ public static function normalize(string $str) {
100
+ $str = Normalizer::normalize($str, Normalizer::FORM_D);
101
+ $str = preg_replace('/\p{Mn}/u', '', $str);
102
+ return $str;
103
+ }
104
+
105
+
106
+ public static function slug(string $str, $sep = ''): string
74
107
  {
108
+ $str = self::normalize($str);
75
109
  $str = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $str);
76
110
  $str = strtolower($str);
77
- $str = preg_replace('/[^a-z0-9]/', '', $str);
111
+ $str = preg_replace('/[^a-z0-9]/', $sep, $str);
78
112
  return $str;
79
113
  }
80
114
 
@@ -1,29 +1,29 @@
1
1
  <?php
2
2
 
3
3
  /**
4
- * YAML — Parseur YAML léger, entièrement statique.
4
+ * YAML — lightweight, fully static YAML parser.
5
5
  *
6
- * Utilisation :
7
- * $data = YAML::parse($yamlString); // mappings → stdClass (défaut)
8
- * $data = YAML::parse($yamlString, true); // mappings → array associatif
9
- * $data = YAML::parseFile('/chemin/vers/config.yaml');
6
+ * Usage:
7
+ * $data = YAML::parse($yamlString); // mappings → stdClass (default)
8
+ * $data = YAML::parse($yamlString, true); // mappings → associative array
9
+ * $data = YAML::parseFile('/path/to/config.yaml');
10
10
  *
11
- * Supporte :
12
- * - Scalaires typés (string, int, float, bool, null)
13
- * - Guillemets simples et doubles (avec séquences d'échappement)
14
- * - Blocs multi-lignes (| littéral et > replié, avec chomping -, +)
15
- * - Plain scalars multi-lignes (continuation sans indicateur)
16
- * - Mappings et séquences imbriqués
17
- * - Collections inline [a, b] et {k: v}
18
- * - Commentaires (#)
19
- * - Documents multiples séparés par ---
11
+ * Supports:
12
+ * - Typed scalars (string, int, float, bool, null)
13
+ * - Single and double quotes (with escape sequences)
14
+ * - Multi-line blocks (| literal and > folded, with chomping -, +)
15
+ * - Multi-line plain scalars (continuation with no indicator)
16
+ * - Nested mappings and sequences
17
+ * - Inline collections [a, b] and {k: v}
18
+ * - Comments (#)
19
+ * - Multiple documents separated by ---
20
20
  */
21
21
  class YAML
22
22
  {
23
23
  private function __construct() {}
24
24
 
25
25
  // -------------------------------------------------------------------------
26
- // Points d'entrée publics
26
+ // Public entry points
27
27
  // -------------------------------------------------------------------------
28
28
 
29
29
  public static function parse(string $yaml, bool $assoc = false): mixed
@@ -49,60 +49,60 @@ class YAML
49
49
  public static function parseFile(string $path, bool $assoc = false): mixed
50
50
  {
51
51
  if (!is_readable($path)) {
52
- throw new \RuntimeException("Impossible de lire le fichier : $path");
52
+ throw new \RuntimeException("Cannot read file: $path");
53
53
  }
54
54
  return self::parse(file_get_contents($path), $assoc);
55
55
  }
56
56
 
57
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.
58
+ * Loads a YAML or JSON file, then walks the result recursively and replaces
59
+ * any string value that matches a relative path to an existing YAML/JSON
60
+ * file with that file's deserialized content.
61
61
  *
62
- * Chaque fichier inclus est lui-même résolu relativement à son propre
63
- * répertoire, et ainsi de suite (récursif).
62
+ * Each included file is itself resolved relative to its own directory, and
63
+ * so on (recursive).
64
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.
65
+ * If the string doesn't end in .yml/.yaml/.json, or the resolved file
66
+ * doesn't exist, the value is kept as-is.
67
67
  *
68
- * Les références circulaires (ex. A → B → A) lèvent une RuntimeException.
68
+ * Circular references (e.g. A → B → A) throw a RuntimeException.
69
69
  *
70
- * Utilisation :
71
- * $data = YAML::loadFile('/chemin/vers/config.yaml');
72
- * $data = YAML::loadFile('/chemin/vers/config.yaml', true); // arrays assoc
70
+ * Usage:
71
+ * $data = YAML::loadFile('/path/to/config.yaml');
72
+ * $data = YAML::loadFile('/path/to/config.yaml', true); // assoc arrays
73
73
  *
74
- * @param string $path Chemin vers le fichier racine (YAML ou JSON).
75
- * @param bool $assoc true → mappings en array, false → stdClass.
74
+ * @param string $path Path to the root file (YAML or JSON).
75
+ * @param bool $assoc true → mappings as arrays, false → stdClass.
76
76
  * @return mixed
77
77
  */
78
78
  public static function loadFile(string $path, bool $assoc = false): mixed
79
79
  {
80
80
  $absolute = realpath($path);
81
81
  if ($absolute === false || !is_readable($absolute)) {
82
- throw new \RuntimeException("Impossible de lire le fichier : $path");
82
+ throw new \RuntimeException("Cannot read file: $path");
83
83
  }
84
84
 
85
85
  return self::loadFileRecursive($absolute, $assoc, []);
86
86
  }
87
87
 
88
88
  // -------------------------------------------------------------------------
89
- // Méthodes privées pour loadFile
89
+ // Private methods for loadFile
90
90
  // -------------------------------------------------------------------------
91
91
 
92
92
  /**
93
- * Charge et résout un fichier, en propageant la liste des ancêtres pour
94
- * détecter les cycles.
93
+ * Loads and resolves a file, propagating the list of ancestors to detect
94
+ * cycles.
95
95
  *
96
- * @param string $absolute Chemin absolu canonique du fichier à charger.
96
+ * @param string $absolute Canonical absolute path of the file to load.
97
97
  * @param bool $assoc
98
- * @param string[] $ancestors Chemins absolus des fichiers en cours de traitement.
98
+ * @param string[] $ancestors Absolute paths of the files currently being processed.
99
99
  * @return mixed
100
100
  */
101
101
  private static function loadFileRecursive(string $absolute, bool $assoc, array $ancestors): mixed
102
102
  {
103
103
  if (in_array($absolute, $ancestors, true)) {
104
104
  throw new \RuntimeException(
105
- "Référence circulaire détectée : " . implode(' → ', $ancestors) . " → $absolute"
105
+ "Circular reference detected: " . implode(' → ', $ancestors) . " → $absolute"
106
106
  );
107
107
  }
108
108
 
@@ -113,7 +113,7 @@ class YAML
113
113
  if ($ext === 'json') {
114
114
  $data = json_decode($raw, $assoc, 512, JSON_THROW_ON_ERROR);
115
115
  } else {
116
- // .yml, .yaml ou autre extension traitée comme YAML
116
+ // .yml, .yaml or any other extension treated as YAML
117
117
  $data = self::parse($raw, $assoc);
118
118
  }
119
119
 
@@ -121,11 +121,11 @@ class YAML
121
121
  }
122
122
 
123
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.
124
+ * Recursively walks a PHP value (stdClass object, array, string, scalar)
125
+ * and resolves references to external files.
126
126
  *
127
127
  * @param mixed $node
128
- * @param string $dir Répertoire du fichier qui contient ce nœud.
128
+ * @param string $dir Directory of the file that contains this node.
129
129
  * @param bool $assoc
130
130
  * @param string[] $ancestors
131
131
  * @return mixed
@@ -150,14 +150,14 @@ class YAML
150
150
  return $node;
151
151
  }
152
152
 
153
- // int, float, bool, null → retourné tel quel
153
+ // int, float, bool, null → returned as-is
154
154
  return $node;
155
155
  }
156
156
 
157
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.
158
+ * If the string points to an existing YAML/JSON file (path relative to the
159
+ * $dir directory), loads that file recursively. Otherwise returns the
160
+ * original string.
161
161
  *
162
162
  * @param string $str
163
163
  * @param string $dir
@@ -169,24 +169,24 @@ class YAML
169
169
  {
170
170
  $trimmed = trim($str);
171
171
 
172
- // Filtre rapide sur l'extension
172
+ // Quick filter on the extension
173
173
  if (!preg_match('/\.(ya?ml|json)$/i', $trimmed)) {
174
174
  return $str;
175
175
  }
176
176
 
177
- // Résolution du chemin relatif au répertoire du fichier parent
177
+ // Resolve the path relative to the parent file's directory
178
178
  $candidate = $dir . DIRECTORY_SEPARATOR . $trimmed;
179
179
  $absolute = realpath($candidate);
180
180
 
181
181
  if ($absolute === false || !is_readable($absolute)) {
182
- return $str; // Fichier introuvable → string ordinaire
182
+ return $str; // File not found → plain string
183
183
  }
184
184
 
185
185
  return self::loadFileRecursive($absolute, $assoc, $ancestors);
186
186
  }
187
187
 
188
188
  // -------------------------------------------------------------------------
189
- // Parsing récursif
189
+ // Recursive parsing
190
190
  // -------------------------------------------------------------------------
191
191
 
192
192
  private static function parseBlock(array $lines, int &$pos, int $indent, bool $assoc = false): mixed
@@ -231,13 +231,13 @@ class YAML
231
231
  $pos++;
232
232
 
233
233
  if ($rest === null) {
234
- // Valeur sur les lignes suivantes : sous-bloc ou plain scalar multi-ligne
234
+ // Value on the following lines: sub-block or multi-line plain scalar
235
235
  self::skipEmptyAndComments($lines, $pos);
236
236
  if ($pos < count($lines)) {
237
237
  $nextIndent = self::getIndent($lines[$pos]);
238
238
  if ($nextIndent > $indent) {
239
239
  $nextTrimmed = ltrim($lines[$pos]);
240
- // Plain scalar si ce n'est ni un mapping ni une séquence
240
+ // Plain scalar if it's neither a mapping nor a sequence
241
241
  if (!self::isMapping($nextTrimmed) && !str_starts_with($nextTrimmed, '- ')) {
242
242
  $result[$key] = self::parseScalar(
243
243
  self::collectPlainScalar($lines, $pos, $indent, '')
@@ -258,7 +258,7 @@ class YAML
258
258
  } elseif ($rest !== '' && ($rest[0] === '[' || $rest[0] === '{')) {
259
259
  $result[$key] = self::parseInlineCollection($rest, $assoc);
260
260
  } else {
261
- // Scalaire inline, peut être suivi de lignes de continuation
261
+ // Inline scalar, may be followed by continuation lines
262
262
  $result[$key] = self::parseScalar(
263
263
  self::collectPlainScalar($lines, $pos, $indent, $rest)
264
264
  );
@@ -299,10 +299,10 @@ class YAML
299
299
  $result[] = null;
300
300
  }
301
301
  } elseif (self::isMapping($itemContent)) {
302
- // Mapping inline dans la séquence :
303
- // On reconstruit un tableau de lignes virtuel en préfixant la première clé
304
- // avec fakeIndent, puis on délègue entièrement à parseMapping pour bénéficier
305
- // de toute sa logique (|, >, plain scalars, sous-blocs…).
302
+ // Inline mapping inside the sequence:
303
+ // Rebuild a virtual array of lines by prefixing the first key
304
+ // with fakeIndent, then delegate entirely to parseMapping to get
305
+ // all of its logic (|, >, plain scalars, sub-blocks…).
306
306
  $fakeIndent = $lineIndent + 2;
307
307
  $fakePrefix = str_repeat(' ', $fakeIndent);
308
308
  $virtualLines = array_merge(
@@ -324,12 +324,12 @@ class YAML
324
324
  }
325
325
 
326
326
  // -------------------------------------------------------------------------
327
- // Plain scalar multi-ligne
327
+ // Multi-line plain scalar
328
328
  // -------------------------------------------------------------------------
329
329
 
330
330
  /**
331
- * Collecte un scalaire qui peut continuer sur des lignes plus indentées que $parentIndent.
332
- * Les lignes de continuation sont jointes par un espace (repli implicite).
331
+ * Collects a scalar that may continue on lines more indented than $parentIndent.
332
+ * Continuation lines are joined with a space (implicit folding).
333
333
  */
334
334
  private static function collectPlainScalar(array $lines, int &$pos, int $parentIndent, string $first): string
335
335
  {
@@ -339,18 +339,18 @@ class YAML
339
339
  $raw = $lines[$pos];
340
340
  $trimmed = trim($raw);
341
341
 
342
- // Ligne vide : fin du scalaire
342
+ // Blank line: end of the scalar
343
343
  if ($trimmed === '') break;
344
344
 
345
345
  $lineIndent = self::getIndent($raw);
346
346
 
347
- // Retour à l'indentation parente ou moins : fin
347
+ // Back to the parent indentation or less: end
348
348
  if ($lineIndent <= $parentIndent) break;
349
349
 
350
- // Commentaire seul sur la ligne : fin
350
+ // Comment alone on the line: end
351
351
  if (str_starts_with($trimmed, '#')) break;
352
352
 
353
- // C'est un mapping ou une séquence : fin
353
+ // It's a mapping or a sequence: end
354
354
  if (self::isMapping($trimmed) || str_starts_with($trimmed, '- ')) break;
355
355
 
356
356
  $parts[] = $trimmed;
@@ -361,7 +361,7 @@ class YAML
361
361
  }
362
362
 
363
363
  // -------------------------------------------------------------------------
364
- // Blocs multi-lignes
364
+ // Multi-line blocks
365
365
  // -------------------------------------------------------------------------
366
366
 
367
367
  private static function parseLiteralBlock(array $lines, int &$pos, int $parentIndent, string $indicator): string
@@ -435,7 +435,7 @@ class YAML
435
435
  }
436
436
 
437
437
  // -------------------------------------------------------------------------
438
- // Collections inline [a, b] {k: v}
438
+ // Inline collections [a, b] {k: v}
439
439
  // -------------------------------------------------------------------------
440
440
 
441
441
  private static function parseInlineCollection(string $raw, bool $assoc = false): mixed
@@ -500,7 +500,7 @@ class YAML
500
500
  }
501
501
 
502
502
  // -------------------------------------------------------------------------
503
- // Scalaires
503
+ // Scalars
504
504
  // -------------------------------------------------------------------------
505
505
 
506
506
  private static function parseScalar(string $value): mixed
@@ -534,7 +534,7 @@ class YAML
534
534
  }
535
535
 
536
536
  // -------------------------------------------------------------------------
537
- // Utilitaires
537
+ // Helpers
538
538
  // -------------------------------------------------------------------------
539
539
 
540
540
  private static function getIndent(string $line): int
@@ -0,0 +1,9 @@
1
+ import path from "path";
2
+ import getFileStats from "../utils/getfilestats.js";
3
+
4
+ const __root = process.cwd();
5
+
6
+ export default async function fstat(php, msg) {
7
+ const file = path.join(__root, msg.path);
8
+ return getFileStats(file);
9
+ }