Skip to content

Commit

Permalink
[Regex] add first_match() and every_match() functions (#151)
Browse files Browse the repository at this point in the history
  • Loading branch information
veewee authored Apr 9, 2021
1 parent 0753087 commit 5a7fde2
Show file tree
Hide file tree
Showing 11 changed files with 481 additions and 16 deletions.
2 changes: 1 addition & 1 deletion docs/component/json.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@

- [decode](./../../src/Psl/Json/decode.php#L24)
- [encode](./../../src/Psl/Json/encode.php#L27)
- [typed](./../../src/Psl/Json/typed.php#L22)
- [typed](./../../src/Psl/Json/typed.php#L20)


3 changes: 3 additions & 0 deletions docs/component/regex.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@

#### `Functions`

- [capture_groups](./../../src/Psl/Regex/capture_groups.php#L17)
- [every_match](./../../src/Psl/Regex/every_match.php#L25)
- [first_match](./../../src/Psl/Regex/first_match.php#L24)
- [matches](./../../src/Psl/Regex/matches.php#L19)
- [replace](./../../src/Psl/Regex/replace.php#L26)
- [replace_every](./../../src/Psl/Regex/replace_every.php#L27)
Expand Down
2 changes: 1 addition & 1 deletion integration/Psalm/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

/**
* @deprecated use `php-standard-library/psalm-plugin` package instead.
*
*
* @see https://github.com/php-standard-library/psalm-plugin
*/
final class Plugin implements PluginEntryPointInterface
Expand Down
3 changes: 3 additions & 0 deletions src/Psl/Internal/Loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,9 @@ final class Loader
'Psl\Math\tan',
'Psl\Math\to_base',
'Psl\Result\wrap',
'Psl\Regex\capture_groups',
'Psl\Regex\every_match',
'Psl\Regex\first_match',
'Psl\Regex\split',
'Psl\Regex\matches',
'Psl\Regex\replace',
Expand Down
19 changes: 5 additions & 14 deletions src/Psl/Json/typed.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,24 @@

namespace Psl\Json;

use Psl\Type\Exception\AssertException;
use Psl\Type\Exception\CoercionException;
use Psl\Type\TypeInterface;
use Psl\Type;

/**
* Decode a json encoded string into a dynamic variable.
*
* @template T
*
* @param TypeInterface<T> $type
* @param Type\TypeInterface<T> $type
*
* @throws Exception\DecodeException If an error occurred.
*
* @return T
*/
function typed(string $json, TypeInterface $type)
function typed(string $json, Type\TypeInterface $type)
{
$value = decode($json);

try {
return $type->assert($value);
} catch (AssertException $e) {
}

try {
return $type->coerce($value);
} catch (CoercionException $e) {
return $type->coerce(decode($json));
} catch (Type\Exception\CoercionException $e) {
throw new Exception\DecodeException($e->getMessage(), (int)$e->getCode(), $e);
}
}
28 changes: 28 additions & 0 deletions src/Psl/Regex/capture_groups.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Psl\Regex;

use Psl\Dict;
use Psl\Type;

/**
* @param list<array-key> $groups
*
* @return Type\TypeInterface<array<array-key, string>>
*
* @psalm-suppress MixedReturnTypeCoercion - Psalm loses track of the keys. No worries, another psalm plugin fixes this!
*/
function capture_groups(array $groups): Type\TypeInterface
{
return Type\shape(
Dict\from_keys(
Dict\unique([0, ...$groups]),
/**
* @return Type\TypeInterface<string>
*/
static fn(): Type\TypeInterface => Type\string()
)
);
}
52 changes: 52 additions & 0 deletions src/Psl/Regex/every_match.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

namespace Psl\Regex;

use Psl\Exception\InvariantViolationException;
use Psl\Type;

use function preg_match_all;

/**
* Determine if $subject matches the given $pattern and return every matches.
*
* @template T of array|null
*
* @param non-empty-string $pattern The pattern to match against.
* @param ?Type\TypeInterface<T> $capture_groups What shape does a single set of matching items have?
*
* @throws Exception\RuntimeException If an internal error accord.
* @throws Exception\InvalidPatternException If $pattern is invalid.
*
* @return (T is null ? list<array<array-key, string>> : list<T>)|null
*/
function every_match(
string $subject,
string $pattern,
?Type\TypeInterface $capture_groups = null,
int $offset = 0
): ?array {
$matching = Internal\call_preg(
'preg_match_all',
static function () use ($subject, $pattern, $offset): ?array {
$matching = [];
$matches = preg_match_all($pattern, $subject, $matching, PREG_SET_ORDER, $offset);

return $matches === 0 ? null : $matching;
}
);

if ($matching === null) {
return null;
}

$capture_groups ??= Type\dict(Type\array_key(), Type\string());

try {
return Type\vec($capture_groups)->coerce($matching);
} catch (InvariantViolationException | Type\Exception\CoercionException $e) {
throw new Exception\RuntimeException('Invalid capture groups', 0, $e);
}
}
51 changes: 51 additions & 0 deletions src/Psl/Regex/first_match.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace Psl\Regex;

use Psl\Type;

use function preg_match;

/**
* Determine if $subject matches the given $pattern and return the first matches.
*
* @template T of array|null
*
* @param non-empty-string $pattern The pattern to match against.
* @param ?Type\TypeInterface<T> $capture_groups What shape does the matching items have?
*
* @throws Exception\RuntimeException If an internal error accord.
* @throws Exception\InvalidPatternException If $pattern is invalid.
*
* @return (T is null ? array<array-key, string> : T)|null
*/
function first_match(
string $subject,
string $pattern,
?Type\TypeInterface $capture_groups = null,
int $offset = 0
): ?array {
$matching = Internal\call_preg(
'preg_match',
static function () use ($subject, $pattern, $offset): ?array {
$matching = [];
$matches = preg_match($pattern, $subject, $matching, 0, $offset);

return $matches === 0 ? null : $matching;
}
);

if ($matching === null) {
return null;
}

$capture_groups ??= Type\dict(Type\array_key(), Type\string());

try {
return $capture_groups->coerce($matching);
} catch (Type\Exception\CoercionException $e) {
throw new Exception\RuntimeException('Invalid capture groups', 0, $e);
}
}
21 changes: 21 additions & 0 deletions tests/Psl/Regex/CaptureGroupsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Psl\Tests\Regex;

use PHPUnit\Framework\TestCase;

use function Psl\Regex\capture_groups;

final class CaptureGroupsTest extends TestCase
{
public function testItAlwaysAddsZeroCaptureResult(): void
{
$data = [0 => 'Hello', 1 => 'World'];
$shape = capture_groups([1]);
$actual = $shape->coerce($data);

static::assertSame($actual, $data);
}
}
Loading

0 comments on commit 5a7fde2

Please sign in to comment.