Skip to content

Commit

Permalink
[Str] add grarpheme support
Browse files Browse the repository at this point in the history
  • Loading branch information
azjezz committed Oct 7, 2020
1 parent f60c106 commit 33fd4e2
Show file tree
Hide file tree
Showing 30 changed files with 989 additions and 1 deletion.
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-mbstring": "*",
"ext-json": "*",
"ext-sodium": "*"
"ext-sodium": "*",
"ext-intl": "*"
},
"require-dev": {
"vimeo/psalm": "dev-master",
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 @@ -328,6 +328,20 @@ final class Loader
'Psl\Password\hash',
'Psl\Password\needs_rehash',
'Psl\Password\verify',
'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);
}
33 changes: 33 additions & 0 deletions src/Psl/Str/Grapheme/ends_with.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

use Psl;

/**
* Returns whether the string ends with the given suffix.
*
* @psalm-pure
*/
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;
}
33 changes: 33 additions & 0 deletions src/Psl/Str/Grapheme/ends_with_ci.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

use Psl;

/**
* Returns whether the string ends with the given suffix (case-insensitive).
*
* @psalm-pure
*/
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;
}
17 changes: 17 additions & 0 deletions src/Psl/Str/Grapheme/length.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

use function grapheme_strlen;

/**
* Returns the length of the given string in grapheme units
*
* @psalm-pure
*/
function length(string $string): int
{
return (int) grapheme_strlen($string);
}
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;
}
39 changes: 39 additions & 0 deletions src/Psl/Str/Grapheme/slice.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

use Psl;
use Psl\Internal;

use function mb_substr;

/**
* 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);
}
18 changes: 18 additions & 0 deletions src/Psl/Str/Grapheme/starts_with.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

use Psl;

/**
* 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);
}
18 changes: 18 additions & 0 deletions src/Psl/Str/Grapheme/starts_with_ci.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Psl\Str\Grapheme;

use Psl;

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

0 comments on commit 33fd4e2

Please sign in to comment.