Skip to content

Commit 60ac7f6

Browse files
committed
Adding inspection engine
1 parent 137dcde commit 60ac7f6

42 files changed

Lines changed: 3663 additions & 1 deletion

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

phpstan-build.neon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,6 @@ parameters:
1919
treatPhpDocTypesAsCertain: false
2020
parallel:
2121
processTimeout: 300.0
22+
bootstrapFiles:
23+
- vendor/autoload.php
2224

phpstan.neon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,6 @@ parameters:
2222
treatPhpDocTypesAsCertain: false
2323
parallel:
2424
processTimeout: 300.0
25+
bootstrapFiles:
26+
- vendor/autoload.php
2527

src/Buffer.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
use CallbackFilterIterator;
1717
use Closure;
1818
use Iterator;
19+
use League\Csv\Inspection\Inspector;
20+
use League\Csv\Inspection\Metadata;
21+
use League\Csv\Inspection\Summary;
1922
use League\Csv\Query\Constraint\Criteria;
2023
use League\Csv\Query\Predicate;
2124
use League\Csv\Serializer\Denormalizer;
@@ -203,6 +206,35 @@ public function map(callable $callback): Iterator
203206
return MapIterator::fromIterable($this->getRecords(), $callback);
204207
}
205208

209+
/**
210+
* @param callable(TInitial|null, array<mixed>, array-key=): TInitial $callback
211+
* @param TInitial|null $initial
212+
*
213+
* @template TInitial
214+
*
215+
* @throws SyntaxError
216+
*
217+
* @return TInitial|null
218+
*/
219+
public function reduce(callable $callback, mixed $initial = null): mixed
220+
{
221+
foreach ($this->getRecords() as $offset => $record) {
222+
$initial = $callback($initial, $record, $offset);
223+
}
224+
225+
return $initial;
226+
}
227+
228+
public function describe(?Inspector $inspector = null): Summary
229+
{
230+
return ($inspector ?? Inspector::default())->describe($this);
231+
}
232+
233+
public function schema(?Inspector $inspector = null): Metadata
234+
{
235+
return ($inspector ?? Inspector::default())->schema($this);
236+
}
237+
206238
/**
207239
* @param non-negative-int $nth
208240
*

src/Inspection/Aggregator.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
/**
4+
* League.Csv (https://csv.thephpleague.com)
5+
*
6+
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace League\Csv\Inspection;
15+
16+
use Generator;
17+
use IteratorAggregate;
18+
use SplQueue;
19+
20+
final class Aggregator implements IteratorAggregate
21+
{
22+
private SplQueue $queue;
23+
private bool $finished = false;
24+
25+
public function __construct()
26+
{
27+
$this->queue = new SplQueue();
28+
}
29+
30+
public function push(mixed $value): void
31+
{
32+
$this->queue->enqueue($value);
33+
}
34+
35+
public function finish(): void
36+
{
37+
$this->finished = true;
38+
}
39+
40+
public function getIterator(): Generator
41+
{
42+
while (!$this->finished || !$this->queue->isEmpty()) {
43+
if (!$this->queue->isEmpty()) {
44+
yield $this->queue->dequeue();
45+
}
46+
}
47+
}
48+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
/**
4+
* League.Csv (https://csv.thephpleague.com)
5+
*
6+
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace League\Csv\Inspection\Calculator;
15+
16+
use League\Csv\Inspection\Strategy\FieldStrategy;
17+
18+
interface FieldCalculator
19+
{
20+
/** @return non-empty-string */
21+
public function name(): string;
22+
23+
public function push(mixed $value): void;
24+
25+
public function calculate(): mixed;
26+
27+
public function reset(): void;
28+
29+
public function supports(FieldStrategy $strategy): bool;
30+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
3+
/**
4+
* League.Csv (https://csv.thephpleague.com)
5+
*
6+
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace League\Csv\Inspection\Calculator;
15+
16+
use DateTimeInterface;
17+
use League\Csv\Inspection\Strategy\DateFieldStrategy;
18+
use League\Csv\Inspection\Strategy\FieldStrategy;
19+
use League\Csv\Inspection\Strategy\NumericFieldStrategy;
20+
use ValueError;
21+
22+
use function is_numeric;
23+
use function trim;
24+
25+
final class MaxFieldCalculator implements FieldCalculator
26+
{
27+
/** @var non-empty-string */
28+
public readonly string $name;
29+
/** @var DateTimeInterface|float|null */
30+
private mixed $value;
31+
private ?string $type;
32+
33+
public function __construct(string $name = 'max')
34+
{
35+
'' !== ($name = trim($name)) || throw new ValueError('The calculator name cannot be empty.');
36+
$this->name = $name;
37+
$this->reset();
38+
}
39+
40+
public function supports(FieldStrategy $strategy): bool
41+
{
42+
return match ($strategy::class) {
43+
NumericFieldStrategy::class,
44+
DateFieldStrategy::class => true,
45+
default => false,
46+
};
47+
}
48+
49+
public function reset(): void
50+
{
51+
$this->value = null;
52+
$this->type = null;
53+
}
54+
55+
public function name(): string
56+
{
57+
return $this->name;
58+
}
59+
60+
public function push(mixed $value): void
61+
{
62+
$type = null;
63+
if (is_numeric($value)) {
64+
$type = 'numeric';
65+
$value = (float) $value;
66+
} elseif ($value instanceof DateTimeInterface) {
67+
$type = 'date';
68+
}
69+
70+
if (null === $type) {
71+
return;
72+
}
73+
74+
$this->type ??= $type;
75+
if ($type !== $this->type) {
76+
return;
77+
}
78+
79+
if (null === $this->value || $value > $this->value) {
80+
$this->value = $value;
81+
}
82+
}
83+
84+
/**
85+
* @return DateTimeInterface|float|null
86+
*/
87+
public function calculate(): mixed
88+
{
89+
return $this->value;
90+
}
91+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
/**
4+
* League.Csv (https://csv.thephpleague.com)
5+
*
6+
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace League\Csv\Inspection\Calculator;
15+
16+
use DateTimeInterface;
17+
use League\Csv\Inspection\Strategy\DateFieldStrategy;
18+
use League\Csv\Inspection\Strategy\FieldStrategy;
19+
use League\Csv\Inspection\Strategy\NumericFieldStrategy;
20+
use ValueError;
21+
22+
use function is_numeric;
23+
use function trim;
24+
25+
final class MinFieldCalculator implements FieldCalculator
26+
{
27+
/** @var non-empty-string */
28+
public readonly string $name;
29+
/** @var DateTimeInterface|float|null */
30+
private mixed $value;
31+
private ?string $type;
32+
33+
public function __construct(string $name = 'min')
34+
{
35+
'' !== ($name = trim($name)) || throw new ValueError('The calculator name cannot be empty.');
36+
$this->name = $name;
37+
$this->reset();
38+
}
39+
40+
public function reset(): void
41+
{
42+
$this->value = null;
43+
$this->type = null;
44+
}
45+
46+
public function name(): string
47+
{
48+
return $this->name;
49+
}
50+
51+
public function supports(FieldStrategy $strategy): bool
52+
{
53+
return match ($strategy::class) {
54+
NumericFieldStrategy::class,
55+
DateFieldStrategy::class => true,
56+
default => false,
57+
};
58+
}
59+
60+
public function push(mixed $value): void
61+
{
62+
$type = null;
63+
if (is_numeric($value)) {
64+
$type = 'numeric';
65+
$value = (float) $value;
66+
} elseif ($value instanceof DateTimeInterface) {
67+
$type = 'date';
68+
}
69+
70+
if (null === $type) {
71+
return;
72+
}
73+
74+
$this->type ??= $type;
75+
if ($type !== $this->type) {
76+
return;
77+
}
78+
79+
if (null === $this->value || $value < $this->value) {
80+
$this->value = $value;
81+
}
82+
}
83+
84+
public function calculate(): mixed
85+
{
86+
return $this->value;
87+
}
88+
}

0 commit comments

Comments
 (0)