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

[Str] add grarpheme support #74

Merged
merged 1 commit into from
Nov 2, 2020
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
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"ext-bcmath": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-sodium": "*"
"ext-sodium": "*",
"ext-intl": "*"
},
"require-dev": {
"phpunit/phpunit": "^9.4",
Expand Down
14 changes: 14 additions & 0 deletions src/Psl/Internal/Loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,20 @@ final class Loader
'Psl\Hash\equals',
'Psl\Hash\Hmac\hash',
'Psl\Hash\Hmac\algorithms',
'Psl\Str\Grapheme\contains',
'Psl\Str\Grapheme\contains_ci',
'Psl\Str\Grapheme\ends_with',
'Psl\Str\Grapheme\ends_with_ci',
'Psl\Str\Grapheme\length',
'Psl\Str\Grapheme\search',
'Psl\Str\Grapheme\search_ci',
'Psl\Str\Grapheme\search_last',
'Psl\Str\Grapheme\search_last_ci',
'Psl\Str\Grapheme\slice',
'Psl\Str\Grapheme\starts_with',
'Psl\Str\Grapheme\starts_with_ci',
'Psl\Str\Grapheme\strip_prefix',
'Psl\Str\Grapheme\strip_suffix',
];

public const INTERFACES = [
Expand Down
30 changes: 30 additions & 0 deletions src/Psl/Str/Grapheme/contains.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

use Psl;

/**
* Returns whether the 'haystack' string contains the 'needle' string.
*
* An optional offset determines where in the haystack the search begins. If the
* offset is negative, the search will begin that many characters from the end
* of the string. If the offset is out-of-bounds, a ViolationException will be
* thrown.
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If the $offset is out-of-bounds.
*/
function contains(string $haystack, string $needle, int $offset = 0): bool
{
if ('' === $needle) {
Psl\Internal\validate_offset($offset, length($haystack));

return true;
}

return null !== search($haystack, $needle, $offset);
}
30 changes: 30 additions & 0 deletions src/Psl/Str/Grapheme/contains_ci.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

use Psl;

/**
* Returns whether the 'haystack' string contains the 'needle' string.
*
* An optional offset determines where in the haystack the search begins. If the
* offset is negative, the search will begin that many characters from the end
* of the string. If the offset is out-of-bounds, a ViolationException will be
* thrown.
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If the $offset is out-of-bounds.
*/
function contains_ci(string $haystack, string $needle, int $offset = 0): bool
{
if ('' === $needle) {
Psl\Internal\validate_offset($offset, length($haystack));

return true;
}

return null !== search_ci($haystack, $needle, $offset);
}
36 changes: 36 additions & 0 deletions src/Psl/Str/Grapheme/ends_with.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

use Psl;

/**
* Returns whether the string ends with the given suffix.
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If unable to convert $string to UTF-16,
* or split it into graphemes.
*/
function ends_with(string $string, string $suffix): bool
{
if ($suffix === $string) {
return true;
}

$suffix_length = length($suffix);
$total_length = length($string);
if ($suffix_length > $total_length) {
return false;
}

/** @psalm-suppress MissingThrowsDocblock */
$position = search_last($string, $suffix);
if (null === $position) {
return false;
}

return $position + $suffix_length === $total_length;
}
36 changes: 36 additions & 0 deletions src/Psl/Str/Grapheme/ends_with_ci.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

use Psl;

/**
* Returns whether the string ends with the given suffix (case-insensitive).
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If unable to convert $string to UTF-16,
* or split it into graphemes.
*/
function ends_with_ci(string $string, string $suffix): bool
{
if ($suffix === $string) {
return true;
}

$suffix_length = length($suffix);
$total_length = length($string);
if ($suffix_length > $total_length) {
return false;
}

/** @psalm-suppress MissingThrowsDocblock */
$position = search_last_ci($string, $suffix);
if (null === $position) {
return false;
}

return $position + $suffix_length === $total_length;
}
27 changes: 27 additions & 0 deletions src/Psl/Str/Grapheme/length.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

use Psl;

use function grapheme_strlen;

/**
* Returns the length of the given string in grapheme units
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If unable to convert $string to UTF-16,
* or split it into graphemes.
*/
function length(string $string): int
{
$length = grapheme_strlen($string);

Psl\invariant(null !== $length, 'unable to convert $string to UTF-16');
Psl\invariant(false !== $length, 'unable to split $string into graphemes');

return $length;
}
34 changes: 34 additions & 0 deletions src/Psl/Str/Grapheme/search.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

use Psl;

use function grapheme_strpos;

/**
* Returns the first position of the 'needle' string in the 'haystack' string
* grapheme units, or null if it isn't found.
*
* An optional offset determines where in the haystack the search begins. If the
* offset is negative, the search will begin that many characters from the end
* of the string.
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If the $offset is out-of-bounds.
*/
function search(string $haystack, string $needle, int $offset = 0): ?int
{
if ('' === $needle) {
return null;
}

$offset = Psl\Internal\validate_offset($offset, length($haystack));

return false === ($pos = grapheme_strpos($haystack, $needle, $offset)) ?
null :
$pos;
}
34 changes: 34 additions & 0 deletions src/Psl/Str/Grapheme/search_ci.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

use Psl;

use function grapheme_stripos;

/**
* Returns the first position of the 'needle' string in the 'haystack' string,
* in grapheme units, or null if it isn't found (case-insensitive).
*
* An optional offset determines where in the haystack the search begins. If the
* offset is negative, the search will begin that many characters from the end
* of the string.
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If $offset is out-of-bounds.
*/
function search_ci(string $haystack, string $needle, int $offset = 0): ?int
{
if ('' === $needle) {
return null;
}

$offset = Psl\Internal\validate_offset($offset, length($haystack));

return false === ($pos = grapheme_stripos($haystack, $needle, $offset)) ?
null :
$pos;
}
35 changes: 35 additions & 0 deletions src/Psl/Str/Grapheme/search_last.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

use Psl;

use function grapheme_strrpos;

/**
* Returns the last position of the 'needle' string in the 'haystack' string,
* or null if it isn't found.
*
* An optional offset determines where in the haystack (from the beginning) the
* search begins. If the offset is negative, the search will begin that many
* characters from the end of the string and go backwards.
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If the $offset is out-of-bounds.
*/
function search_last(string $haystack, string $needle, int $offset = 0): ?int
{
if ('' === $needle) {
return null;
}

$haystack_length = length($haystack);
Psl\invariant($offset >= -$haystack_length && $offset <= $haystack_length, 'Offset is out-of-bounds.');

return false === ($pos = grapheme_strrpos($haystack, $needle, $offset)) ?
null :
$pos;
}
35 changes: 35 additions & 0 deletions src/Psl/Str/Grapheme/search_last_ci.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

use Psl;

use function grapheme_strripos;

/**
* Returns the last position of the 'needle' string in the 'haystack' string,
* or null if it isn't found (case-insensitive).
*
* An optional offset determines where in the haystack (from the beginning) the
* search begins. If the offset is negative, the search will begin that many
* characters from the end of the string and go backwards.
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If the offset is out-of-bounds.
*/
function search_last_ci(string $haystack, string $needle, int $offset = 0): ?int
{
if ('' === $needle) {
return null;
}

$haystack_length = length($haystack);
Psl\invariant($offset >= -$haystack_length && $offset <= $haystack_length, 'Offset is out-of-bounds.');

return false === ($pos = grapheme_strripos($haystack, $needle, $offset)) ?
null :
$pos;
}
36 changes: 36 additions & 0 deletions src/Psl/Str/Grapheme/slice.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

use Psl;

/**
* Returns a substring of length `$length` of the given string starting at the
* `$offset`.
*
* If no length is given, the slice will contain the rest of the
* string. If the length is zero, the empty string will be returned. If the
* offset is out-of-bounds, an InvariantViolationException will be thrown.
*
* @psalm-pure
*
* @throws Psl\Exception\InvariantViolationException If a negative $length is given, or $offset is out-of-bounds.
*/
function slice(string $string, int $offset, ?int $length = null): string
{
Psl\invariant(null === $length || $length >= 0, 'Expected a non-negative length.');
$string_length = length($string);
$offset = Psl\Internal\validate_offset($offset, $string_length);

if (0 === $offset && (null === $length || $string_length <= $length)) {
return $string;
}

if (null === $length) {
return (string) grapheme_substr($string, $offset);
}

return (string) grapheme_substr($string, $offset, $length);
}
16 changes: 16 additions & 0 deletions src/Psl/Str/Grapheme/starts_with.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

/**
* Returns whether the string starts with the given prefix.
*
* @psalm-pure
*/
function starts_with(string $string, string $prefix): bool
{
/** @psalm-suppress MissingThrowsDocblock */
return 0 === search($string, $prefix);
}
Loading