@vituum/vite-plugin-latte 1.2.0 → 1.3.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/package.json +6 -6
- package/vendor/autoload.php +1 -1
- package/vendor/composer/autoload_classmap.php +49 -0
- package/vendor/composer/autoload_real.php +4 -4
- package/vendor/composer/autoload_static.php +51 -2
- package/vendor/composer/installed.json +102 -12
- package/vendor/composer/installed.php +14 -5
- package/vendor/latte/latte/composer.json +6 -5
- package/vendor/latte/latte/readme.md +27 -9
- package/vendor/latte/latte/src/Bridges/Tracy/templates/LattePanel.panel.phtml +4 -1
- package/vendor/latte/latte/src/Latte/Compiler/TagLexer.php +1 -1
- package/vendor/latte/latte/src/Latte/Compiler/TagParserData.php +129 -129
- package/vendor/latte/latte/src/Latte/Compiler/TemplateGenerator.php +1 -1
- package/vendor/latte/latte/src/Latte/Compiler/TemplateParser.php +1 -1
- package/vendor/latte/latte/src/Latte/Engine.php +22 -2
- package/vendor/latte/latte/src/Latte/Essential/CachingIterator.php +2 -3
- package/vendor/latte/latte/src/Latte/Essential/CoreExtension.php +9 -1
- package/vendor/latte/latte/src/Latte/Essential/Filters.php +110 -10
- package/vendor/latte/latte/src/Latte/Essential/Nodes/ImportNode.php +8 -2
- package/vendor/latte/latte/src/Latte/Essential/Nodes/VarNode.php +14 -18
- package/vendor/latte/latte/src/Latte/Essential/TranslatorExtension.php +1 -1
- package/vendor/latte/latte/src/Latte/Loaders/FileLoader.php +5 -4
- package/vendor/latte/latte/src/Latte/Runtime/Template.php +1 -1
- package/vendor/latte/latte/src/Latte/Sandbox/Nodes/FunctionCallNode.php +0 -1
- package/vendor/nette/utils/.phpstorm.meta.php +13 -0
- package/vendor/nette/utils/composer.json +51 -0
- package/vendor/nette/utils/license.md +60 -0
- package/vendor/nette/utils/readme.md +55 -0
- package/vendor/nette/utils/src/HtmlStringable.php +22 -0
- package/vendor/nette/utils/src/Iterators/CachingIterator.php +150 -0
- package/vendor/nette/utils/src/Iterators/Mapper.php +33 -0
- package/vendor/nette/utils/src/SmartObject.php +140 -0
- package/vendor/nette/utils/src/StaticClass.php +34 -0
- package/vendor/nette/utils/src/Translator.php +25 -0
- package/vendor/nette/utils/src/Utils/ArrayHash.php +106 -0
- package/vendor/nette/utils/src/Utils/ArrayList.php +136 -0
- package/vendor/nette/utils/src/Utils/Arrays.php +553 -0
- package/vendor/nette/utils/src/Utils/Callback.php +137 -0
- package/vendor/nette/utils/src/Utils/DateTime.php +140 -0
- package/vendor/nette/utils/src/Utils/FileInfo.php +69 -0
- package/vendor/nette/utils/src/Utils/FileSystem.php +326 -0
- package/vendor/nette/utils/src/Utils/Finder.php +510 -0
- package/vendor/nette/utils/src/Utils/Floats.php +107 -0
- package/vendor/nette/utils/src/Utils/Helpers.php +104 -0
- package/vendor/nette/utils/src/Utils/Html.php +839 -0
- package/vendor/nette/utils/src/Utils/Image.php +831 -0
- package/vendor/nette/utils/src/Utils/ImageColor.php +75 -0
- package/vendor/nette/utils/src/Utils/ImageType.php +25 -0
- package/vendor/nette/utils/src/Utils/Iterables.php +238 -0
- package/vendor/nette/utils/src/Utils/Json.php +84 -0
- package/vendor/nette/utils/src/Utils/ObjectHelpers.php +229 -0
- package/vendor/nette/utils/src/Utils/Paginator.php +245 -0
- package/vendor/nette/utils/src/Utils/Random.php +52 -0
- package/vendor/nette/utils/src/Utils/Reflection.php +322 -0
- package/vendor/nette/utils/src/Utils/ReflectionMethod.php +36 -0
- package/vendor/nette/utils/src/Utils/Strings.php +728 -0
- package/vendor/nette/utils/src/Utils/Type.php +267 -0
- package/vendor/nette/utils/src/Utils/Validators.php +416 -0
- package/vendor/nette/utils/src/Utils/exceptions.php +50 -0
- package/vendor/nette/utils/src/compatibility.php +32 -0
- package/vendor/nette/utils/src/exceptions.php +109 -0
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This file is part of the Nette Framework (https://nette.org)
|
|
5
|
+
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
declare(strict_types=1);
|
|
9
|
+
|
|
10
|
+
namespace Nette\Utils;
|
|
11
|
+
|
|
12
|
+
use Nette;
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Finder allows searching through directory trees using iterator.
|
|
17
|
+
*
|
|
18
|
+
* Finder::findFiles('*.php')
|
|
19
|
+
* ->size('> 10kB')
|
|
20
|
+
* ->from('.')
|
|
21
|
+
* ->exclude('temp');
|
|
22
|
+
*
|
|
23
|
+
* @implements \IteratorAggregate<string, FileInfo>
|
|
24
|
+
*/
|
|
25
|
+
class Finder implements \IteratorAggregate
|
|
26
|
+
{
|
|
27
|
+
use Nette\SmartObject;
|
|
28
|
+
|
|
29
|
+
/** @var array<array{string, string}> */
|
|
30
|
+
private array $find = [];
|
|
31
|
+
|
|
32
|
+
/** @var string[] */
|
|
33
|
+
private array $in = [];
|
|
34
|
+
|
|
35
|
+
/** @var \Closure[] */
|
|
36
|
+
private array $filters = [];
|
|
37
|
+
|
|
38
|
+
/** @var \Closure[] */
|
|
39
|
+
private array $descentFilters = [];
|
|
40
|
+
|
|
41
|
+
/** @var array<string|self> */
|
|
42
|
+
private array $appends = [];
|
|
43
|
+
private bool $childFirst = false;
|
|
44
|
+
|
|
45
|
+
/** @var ?callable */
|
|
46
|
+
private $sort;
|
|
47
|
+
private int $maxDepth = -1;
|
|
48
|
+
private bool $ignoreUnreadableDirs = true;
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Begins search for files and directories matching mask.
|
|
53
|
+
*/
|
|
54
|
+
public static function find(string|array $masks = ['*']): static
|
|
55
|
+
{
|
|
56
|
+
$masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic
|
|
57
|
+
return (new static)->addMask($masks, 'dir')->addMask($masks, 'file');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Begins search for files matching mask.
|
|
63
|
+
*/
|
|
64
|
+
public static function findFiles(string|array $masks = ['*']): static
|
|
65
|
+
{
|
|
66
|
+
$masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic
|
|
67
|
+
return (new static)->addMask($masks, 'file');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Begins search for directories matching mask.
|
|
73
|
+
*/
|
|
74
|
+
public static function findDirectories(string|array $masks = ['*']): static
|
|
75
|
+
{
|
|
76
|
+
$masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic
|
|
77
|
+
return (new static)->addMask($masks, 'dir');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Finds files matching the specified masks.
|
|
83
|
+
*/
|
|
84
|
+
public function files(string|array $masks = ['*']): static
|
|
85
|
+
{
|
|
86
|
+
return $this->addMask((array) $masks, 'file');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Finds directories matching the specified masks.
|
|
92
|
+
*/
|
|
93
|
+
public function directories(string|array $masks = ['*']): static
|
|
94
|
+
{
|
|
95
|
+
return $this->addMask((array) $masks, 'dir');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
private function addMask(array $masks, string $mode): static
|
|
100
|
+
{
|
|
101
|
+
foreach ($masks as $mask) {
|
|
102
|
+
$mask = FileSystem::unixSlashes($mask);
|
|
103
|
+
if ($mode === 'dir') {
|
|
104
|
+
$mask = rtrim($mask, '/');
|
|
105
|
+
}
|
|
106
|
+
if ($mask === '' || ($mode === 'file' && str_ends_with($mask, '/'))) {
|
|
107
|
+
throw new Nette\InvalidArgumentException("Invalid mask '$mask'");
|
|
108
|
+
}
|
|
109
|
+
if (str_starts_with($mask, '**/')) {
|
|
110
|
+
$mask = substr($mask, 3);
|
|
111
|
+
}
|
|
112
|
+
$this->find[] = [$mask, $mode];
|
|
113
|
+
}
|
|
114
|
+
return $this;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Searches in the given directories. Wildcards are allowed.
|
|
120
|
+
*/
|
|
121
|
+
public function in(string|array $paths): static
|
|
122
|
+
{
|
|
123
|
+
$paths = is_array($paths) ? $paths : func_get_args(); // compatibility with variadic
|
|
124
|
+
$this->addLocation($paths, '');
|
|
125
|
+
return $this;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Searches recursively from the given directories. Wildcards are allowed.
|
|
131
|
+
*/
|
|
132
|
+
public function from(string|array $paths): static
|
|
133
|
+
{
|
|
134
|
+
$paths = is_array($paths) ? $paths : func_get_args(); // compatibility with variadic
|
|
135
|
+
$this->addLocation($paths, '/**');
|
|
136
|
+
return $this;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
private function addLocation(array $paths, string $ext): void
|
|
141
|
+
{
|
|
142
|
+
foreach ($paths as $path) {
|
|
143
|
+
if ($path === '') {
|
|
144
|
+
throw new Nette\InvalidArgumentException("Invalid directory '$path'");
|
|
145
|
+
}
|
|
146
|
+
$path = rtrim(FileSystem::unixSlashes($path), '/');
|
|
147
|
+
$this->in[] = $path . $ext;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Lists directory's contents before the directory itself. By default, this is disabled.
|
|
154
|
+
*/
|
|
155
|
+
public function childFirst(bool $state = true): static
|
|
156
|
+
{
|
|
157
|
+
$this->childFirst = $state;
|
|
158
|
+
return $this;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Ignores unreadable directories. By default, this is enabled.
|
|
164
|
+
*/
|
|
165
|
+
public function ignoreUnreadableDirs(bool $state = true): static
|
|
166
|
+
{
|
|
167
|
+
$this->ignoreUnreadableDirs = $state;
|
|
168
|
+
return $this;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Set a compare function for sorting directory entries. The function will be called to sort entries from the same directory.
|
|
174
|
+
* @param callable(FileInfo, FileInfo): int $callback
|
|
175
|
+
*/
|
|
176
|
+
public function sortBy(callable $callback): static
|
|
177
|
+
{
|
|
178
|
+
$this->sort = $callback;
|
|
179
|
+
return $this;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Sorts files in each directory naturally by name.
|
|
185
|
+
*/
|
|
186
|
+
public function sortByName(): static
|
|
187
|
+
{
|
|
188
|
+
$this->sort = fn(FileInfo $a, FileInfo $b): int => strnatcmp($a->getBasename(), $b->getBasename());
|
|
189
|
+
return $this;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Adds the specified paths or appends a new finder that returns.
|
|
195
|
+
*/
|
|
196
|
+
public function append(string|array|null $paths = null): static
|
|
197
|
+
{
|
|
198
|
+
if ($paths === null) {
|
|
199
|
+
return $this->appends[] = new static;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
$this->appends = array_merge($this->appends, (array) $paths);
|
|
203
|
+
return $this;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
/********************* filtering ****************d*g**/
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Skips entries that matches the given masks relative to the ones defined with the in() or from() methods.
|
|
212
|
+
*/
|
|
213
|
+
public function exclude(string|array $masks): static
|
|
214
|
+
{
|
|
215
|
+
$masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic
|
|
216
|
+
foreach ($masks as $mask) {
|
|
217
|
+
$mask = FileSystem::unixSlashes($mask);
|
|
218
|
+
if (!preg_match('~^/?(\*\*/)?(.+)(/\*\*|/\*|/|)$~D', $mask, $m)) {
|
|
219
|
+
throw new Nette\InvalidArgumentException("Invalid mask '$mask'");
|
|
220
|
+
}
|
|
221
|
+
$end = $m[3];
|
|
222
|
+
$re = $this->buildPattern($m[2]);
|
|
223
|
+
$filter = fn(FileInfo $file): bool => ($end && !$file->isDir())
|
|
224
|
+
|| !preg_match($re, FileSystem::unixSlashes($file->getRelativePathname()));
|
|
225
|
+
|
|
226
|
+
$this->descentFilter($filter);
|
|
227
|
+
if ($end !== '/*') {
|
|
228
|
+
$this->filter($filter);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return $this;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Yields only entries which satisfy the given filter.
|
|
238
|
+
* @param callable(FileInfo): bool $callback
|
|
239
|
+
*/
|
|
240
|
+
public function filter(callable $callback): static
|
|
241
|
+
{
|
|
242
|
+
$this->filters[] = \Closure::fromCallable($callback);
|
|
243
|
+
return $this;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* It descends only to directories that match the specified filter.
|
|
249
|
+
* @param callable(FileInfo): bool $callback
|
|
250
|
+
*/
|
|
251
|
+
public function descentFilter(callable $callback): static
|
|
252
|
+
{
|
|
253
|
+
$this->descentFilters[] = \Closure::fromCallable($callback);
|
|
254
|
+
return $this;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Sets the maximum depth of entries.
|
|
260
|
+
*/
|
|
261
|
+
public function limitDepth(?int $depth): static
|
|
262
|
+
{
|
|
263
|
+
$this->maxDepth = $depth ?? -1;
|
|
264
|
+
return $this;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Restricts the search by size. $operator accepts "[operator] [size] [unit]" example: >=10kB
|
|
270
|
+
*/
|
|
271
|
+
public function size(string $operator, ?int $size = null): static
|
|
272
|
+
{
|
|
273
|
+
if (func_num_args() === 1) { // in $operator is predicate
|
|
274
|
+
if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?((?:\d*\.)?\d+)\s*(K|M|G|)B?$#Di', $operator, $matches)) {
|
|
275
|
+
throw new Nette\InvalidArgumentException('Invalid size predicate format.');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
[, $operator, $size, $unit] = $matches;
|
|
279
|
+
$units = ['' => 1, 'k' => 1e3, 'm' => 1e6, 'g' => 1e9];
|
|
280
|
+
$size *= $units[strtolower($unit)];
|
|
281
|
+
$operator = $operator ?: '=';
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return $this->filter(fn(FileInfo $file): bool => !$file->isFile() || Helpers::compare($file->getSize(), $operator, $size));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Restricts the search by modified time. $operator accepts "[operator] [date]" example: >1978-01-23
|
|
290
|
+
*/
|
|
291
|
+
public function date(string $operator, string|int|\DateTimeInterface|null $date = null): static
|
|
292
|
+
{
|
|
293
|
+
if (func_num_args() === 1) { // in $operator is predicate
|
|
294
|
+
if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?(.+)$#Di', $operator, $matches)) {
|
|
295
|
+
throw new Nette\InvalidArgumentException('Invalid date predicate format.');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
[, $operator, $date] = $matches;
|
|
299
|
+
$operator = $operator ?: '=';
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
$date = DateTime::from($date)->format('U');
|
|
303
|
+
return $this->filter(fn(FileInfo $file): bool => !$file->isFile() || Helpers::compare($file->getMTime(), $operator, $date));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
/********************* iterator generator ****************d*g**/
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Returns an array with all found files and directories.
|
|
312
|
+
* @return list<FileInfo>
|
|
313
|
+
*/
|
|
314
|
+
public function collect(): array
|
|
315
|
+
{
|
|
316
|
+
return iterator_to_array($this->getIterator(), preserve_keys: false);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
/** @return \Generator<string, FileInfo> */
|
|
321
|
+
public function getIterator(): \Generator
|
|
322
|
+
{
|
|
323
|
+
$plan = $this->buildPlan();
|
|
324
|
+
foreach ($plan as $dir => $searches) {
|
|
325
|
+
yield from $this->traverseDir($dir, $searches);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
foreach ($this->appends as $item) {
|
|
329
|
+
if ($item instanceof self) {
|
|
330
|
+
yield from $item->getIterator();
|
|
331
|
+
} else {
|
|
332
|
+
$item = FileSystem::platformSlashes($item);
|
|
333
|
+
yield $item => new FileInfo($item);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* @param array<object{pattern: string, mode: string, recursive: bool}> $searches
|
|
341
|
+
* @param string[] $subdirs
|
|
342
|
+
* @return \Generator<string, FileInfo>
|
|
343
|
+
*/
|
|
344
|
+
private function traverseDir(string $dir, array $searches, array $subdirs = []): \Generator
|
|
345
|
+
{
|
|
346
|
+
if ($this->maxDepth >= 0 && count($subdirs) > $this->maxDepth) {
|
|
347
|
+
return;
|
|
348
|
+
} elseif (!is_dir($dir)) {
|
|
349
|
+
throw new Nette\InvalidStateException(sprintf("Directory '%s' does not exist.", rtrim($dir, '/\\')));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
$pathNames = new \FilesystemIterator($dir, \FilesystemIterator::FOLLOW_SYMLINKS | \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::UNIX_PATHS);
|
|
354
|
+
} catch (\UnexpectedValueException $e) {
|
|
355
|
+
if ($this->ignoreUnreadableDirs) {
|
|
356
|
+
return;
|
|
357
|
+
} else {
|
|
358
|
+
throw new Nette\InvalidStateException($e->getMessage());
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
$files = $this->convertToFiles($pathNames, implode('/', $subdirs), FileSystem::isAbsolute($dir));
|
|
363
|
+
|
|
364
|
+
if ($this->sort) {
|
|
365
|
+
$files = iterator_to_array($files);
|
|
366
|
+
usort($files, $this->sort);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
foreach ($files as $file) {
|
|
370
|
+
$pathName = $file->getPathname();
|
|
371
|
+
$cache = $subSearch = [];
|
|
372
|
+
|
|
373
|
+
if ($file->isDir()) {
|
|
374
|
+
foreach ($searches as $search) {
|
|
375
|
+
if ($search->recursive && $this->proveFilters($this->descentFilters, $file, $cache)) {
|
|
376
|
+
$subSearch[] = $search;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if ($this->childFirst && $subSearch) {
|
|
382
|
+
yield from $this->traverseDir($pathName, $subSearch, array_merge($subdirs, [$file->getBasename()]));
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
$relativePathname = FileSystem::unixSlashes($file->getRelativePathname());
|
|
386
|
+
foreach ($searches as $search) {
|
|
387
|
+
if (
|
|
388
|
+
$file->{'is' . $search->mode}()
|
|
389
|
+
&& preg_match($search->pattern, $relativePathname)
|
|
390
|
+
&& $this->proveFilters($this->filters, $file, $cache)
|
|
391
|
+
) {
|
|
392
|
+
yield $pathName => $file;
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (!$this->childFirst && $subSearch) {
|
|
398
|
+
yield from $this->traverseDir($pathName, $subSearch, array_merge($subdirs, [$file->getBasename()]));
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
private function convertToFiles(iterable $pathNames, string $relativePath, bool $absolute): \Generator
|
|
405
|
+
{
|
|
406
|
+
foreach ($pathNames as $pathName) {
|
|
407
|
+
if (!$absolute) {
|
|
408
|
+
$pathName = preg_replace('~\.?/~A', '', $pathName);
|
|
409
|
+
}
|
|
410
|
+
$pathName = FileSystem::platformSlashes($pathName);
|
|
411
|
+
yield new FileInfo($pathName, $relativePath);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
private function proveFilters(array $filters, FileInfo $file, array &$cache): bool
|
|
417
|
+
{
|
|
418
|
+
foreach ($filters as $filter) {
|
|
419
|
+
$res = &$cache[spl_object_id($filter)];
|
|
420
|
+
$res ??= $filter($file);
|
|
421
|
+
if (!$res) {
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return true;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
/** @return array<string, array<object{pattern: string, mode: string, recursive: bool}>> */
|
|
431
|
+
private function buildPlan(): array
|
|
432
|
+
{
|
|
433
|
+
$plan = $dirCache = [];
|
|
434
|
+
foreach ($this->find as [$mask, $mode]) {
|
|
435
|
+
$splits = [];
|
|
436
|
+
if (FileSystem::isAbsolute($mask)) {
|
|
437
|
+
if ($this->in) {
|
|
438
|
+
throw new Nette\InvalidStateException("You cannot combine the absolute path in the mask '$mask' and the directory to search '{$this->in[0]}'.");
|
|
439
|
+
}
|
|
440
|
+
$splits[] = self::splitRecursivePart($mask);
|
|
441
|
+
} else {
|
|
442
|
+
foreach ($this->in ?: ['.'] as $in) {
|
|
443
|
+
$in = strtr($in, ['[' => '[[]', ']' => '[]]']); // in path, do not treat [ and ] as a pattern by glob()
|
|
444
|
+
$splits[] = self::splitRecursivePart($in . '/' . $mask);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
foreach ($splits as [$base, $rest, $recursive]) {
|
|
449
|
+
$base = $base === '' ? '.' : $base;
|
|
450
|
+
$dirs = $dirCache[$base] ??= strpbrk($base, '*?[')
|
|
451
|
+
? glob($base, GLOB_NOSORT | GLOB_ONLYDIR | GLOB_NOESCAPE)
|
|
452
|
+
: [strtr($base, ['[[]' => '[', '[]]' => ']'])]; // unescape [ and ]
|
|
453
|
+
|
|
454
|
+
if (!$dirs) {
|
|
455
|
+
throw new Nette\InvalidStateException(sprintf("Directory '%s' does not exist.", rtrim($base, '/\\')));
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
$search = (object) ['pattern' => $this->buildPattern($rest), 'mode' => $mode, 'recursive' => $recursive];
|
|
459
|
+
foreach ($dirs as $dir) {
|
|
460
|
+
$plan[$dir][] = $search;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return $plan;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Since glob() does not know ** wildcard, we divide the path into a part for glob and a part for manual traversal.
|
|
471
|
+
*/
|
|
472
|
+
private static function splitRecursivePart(string $path): array
|
|
473
|
+
{
|
|
474
|
+
$a = strrpos($path, '/');
|
|
475
|
+
$parts = preg_split('~(?<=^|/)\*\*($|/)~', substr($path, 0, $a + 1), 2);
|
|
476
|
+
return isset($parts[1])
|
|
477
|
+
? [$parts[0], $parts[1] . substr($path, $a + 1), true]
|
|
478
|
+
: [$parts[0], substr($path, $a + 1), false];
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Converts wildcards to regular expression.
|
|
484
|
+
*/
|
|
485
|
+
private function buildPattern(string $mask): string
|
|
486
|
+
{
|
|
487
|
+
if ($mask === '*') {
|
|
488
|
+
return '##';
|
|
489
|
+
} elseif (str_starts_with($mask, './')) {
|
|
490
|
+
$anchor = '^';
|
|
491
|
+
$mask = substr($mask, 2);
|
|
492
|
+
} else {
|
|
493
|
+
$anchor = '(?:^|/)';
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
$pattern = strtr(
|
|
497
|
+
preg_quote($mask, '#'),
|
|
498
|
+
[
|
|
499
|
+
'\*\*/' => '(.+/)?',
|
|
500
|
+
'\*' => '[^/]*',
|
|
501
|
+
'\?' => '[^/]',
|
|
502
|
+
'\[\!' => '[^',
|
|
503
|
+
'\[' => '[',
|
|
504
|
+
'\]' => ']',
|
|
505
|
+
'\-' => '-',
|
|
506
|
+
],
|
|
507
|
+
);
|
|
508
|
+
return '#' . $anchor . $pattern . '$#D' . (defined('PHP_WINDOWS_VERSION_BUILD') ? 'i' : '');
|
|
509
|
+
}
|
|
510
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This file is part of the Nette Framework (https://nette.org)
|
|
5
|
+
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
declare(strict_types=1);
|
|
9
|
+
|
|
10
|
+
namespace Nette\Utils;
|
|
11
|
+
|
|
12
|
+
use Nette;
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Floating-point numbers comparison.
|
|
17
|
+
*/
|
|
18
|
+
class Floats
|
|
19
|
+
{
|
|
20
|
+
use Nette\StaticClass;
|
|
21
|
+
|
|
22
|
+
private const Epsilon = 1e-10;
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
public static function isZero(float $value): bool
|
|
26
|
+
{
|
|
27
|
+
return abs($value) < self::Epsilon;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
public static function isInteger(float $value): bool
|
|
32
|
+
{
|
|
33
|
+
return abs(round($value) - $value) < self::Epsilon;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Compare two floats. If $a < $b it returns -1, if they are equal it returns 0 and if $a > $b it returns 1
|
|
39
|
+
* @throws \LogicException if one of parameters is NAN
|
|
40
|
+
*/
|
|
41
|
+
public static function compare(float $a, float $b): int
|
|
42
|
+
{
|
|
43
|
+
if (is_nan($a) || is_nan($b)) {
|
|
44
|
+
throw new \LogicException('Trying to compare NAN');
|
|
45
|
+
|
|
46
|
+
} elseif (!is_finite($a) && !is_finite($b) && $a === $b) {
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
$diff = abs($a - $b);
|
|
51
|
+
if (($diff < self::Epsilon || ($diff / max(abs($a), abs($b)) < self::Epsilon))) {
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return $a < $b ? -1 : 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Returns true if $a = $b
|
|
61
|
+
* @throws \LogicException if one of parameters is NAN
|
|
62
|
+
*/
|
|
63
|
+
public static function areEqual(float $a, float $b): bool
|
|
64
|
+
{
|
|
65
|
+
return self::compare($a, $b) === 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Returns true if $a < $b
|
|
71
|
+
* @throws \LogicException if one of parameters is NAN
|
|
72
|
+
*/
|
|
73
|
+
public static function isLessThan(float $a, float $b): bool
|
|
74
|
+
{
|
|
75
|
+
return self::compare($a, $b) < 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Returns true if $a <= $b
|
|
81
|
+
* @throws \LogicException if one of parameters is NAN
|
|
82
|
+
*/
|
|
83
|
+
public static function isLessThanOrEqualTo(float $a, float $b): bool
|
|
84
|
+
{
|
|
85
|
+
return self::compare($a, $b) <= 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Returns true if $a > $b
|
|
91
|
+
* @throws \LogicException if one of parameters is NAN
|
|
92
|
+
*/
|
|
93
|
+
public static function isGreaterThan(float $a, float $b): bool
|
|
94
|
+
{
|
|
95
|
+
return self::compare($a, $b) > 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Returns true if $a >= $b
|
|
101
|
+
* @throws \LogicException if one of parameters is NAN
|
|
102
|
+
*/
|
|
103
|
+
public static function isGreaterThanOrEqualTo(float $a, float $b): bool
|
|
104
|
+
{
|
|
105
|
+
return self::compare($a, $b) >= 0;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This file is part of the Nette Framework (https://nette.org)
|
|
5
|
+
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
declare(strict_types=1);
|
|
9
|
+
|
|
10
|
+
namespace Nette\Utils;
|
|
11
|
+
|
|
12
|
+
use Nette;
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Helpers
|
|
16
|
+
{
|
|
17
|
+
/**
|
|
18
|
+
* Executes a callback and returns the captured output as a string.
|
|
19
|
+
*/
|
|
20
|
+
public static function capture(callable $func): string
|
|
21
|
+
{
|
|
22
|
+
ob_start(function () {});
|
|
23
|
+
try {
|
|
24
|
+
$func();
|
|
25
|
+
return ob_get_clean();
|
|
26
|
+
} catch (\Throwable $e) {
|
|
27
|
+
ob_end_clean();
|
|
28
|
+
throw $e;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Returns the last occurred PHP error or an empty string if no error occurred. Unlike error_get_last(),
|
|
35
|
+
* it is nit affected by the PHP directive html_errors and always returns text, not HTML.
|
|
36
|
+
*/
|
|
37
|
+
public static function getLastError(): string
|
|
38
|
+
{
|
|
39
|
+
$message = error_get_last()['message'] ?? '';
|
|
40
|
+
$message = ini_get('html_errors') ? Html::htmlToText($message) : $message;
|
|
41
|
+
$message = preg_replace('#^\w+\(.*?\): #', '', $message);
|
|
42
|
+
return $message;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Converts false to null, does not change other values.
|
|
48
|
+
*/
|
|
49
|
+
public static function falseToNull(mixed $value): mixed
|
|
50
|
+
{
|
|
51
|
+
return $value === false ? null : $value;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Returns value clamped to the inclusive range of min and max.
|
|
57
|
+
*/
|
|
58
|
+
public static function clamp(int|float $value, int|float $min, int|float $max): int|float
|
|
59
|
+
{
|
|
60
|
+
if ($min > $max) {
|
|
61
|
+
throw new Nette\InvalidArgumentException("Minimum ($min) is not less than maximum ($max).");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return min(max($value, $min), $max);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Looks for a string from possibilities that is most similar to value, but not the same (for 8-bit encoding).
|
|
70
|
+
* @param string[] $possibilities
|
|
71
|
+
*/
|
|
72
|
+
public static function getSuggestion(array $possibilities, string $value): ?string
|
|
73
|
+
{
|
|
74
|
+
$best = null;
|
|
75
|
+
$min = (strlen($value) / 4 + 1) * 10 + .1;
|
|
76
|
+
foreach (array_unique($possibilities) as $item) {
|
|
77
|
+
if ($item !== $value && ($len = levenshtein($item, $value, 10, 11, 10)) < $min) {
|
|
78
|
+
$min = $len;
|
|
79
|
+
$best = $item;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return $best;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Compares two values in the same way that PHP does. Recognizes operators: >, >=, <, <=, =, ==, ===, !=, !==, <>
|
|
89
|
+
*/
|
|
90
|
+
public static function compare(mixed $left, string $operator, mixed $right): bool
|
|
91
|
+
{
|
|
92
|
+
return match ($operator) {
|
|
93
|
+
'>' => $left > $right,
|
|
94
|
+
'>=' => $left >= $right,
|
|
95
|
+
'<' => $left < $right,
|
|
96
|
+
'<=' => $left <= $right,
|
|
97
|
+
'=', '==' => $left == $right,
|
|
98
|
+
'===' => $left === $right,
|
|
99
|
+
'!=', '<>' => $left != $right,
|
|
100
|
+
'!==' => $left !== $right,
|
|
101
|
+
default => throw new Nette\InvalidArgumentException("Unknown operator '$operator'"),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|