Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Regex] Introduce *_match() functions to fetch captured data groups #151

Merged
merged 1 commit into from
Apr 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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