Skip to content

Commit b2dfd32

Browse files
authored
feat(support): add words and sentence methods to string utils (#1042)
1 parent 0b27762 commit b2dfd32

File tree

3 files changed

+76
-5
lines changed

3 files changed

+76
-5
lines changed

src/Tempest/Support/src/Str/ManipulatesString.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,22 @@ public function slug(Stringable|string $separator = '-', array $replacements = [
187187
return $this->createOrModify(to_slug($this->value, $separator, $replacements, $replaceSymbols));
188188
}
189189

190+
/**
191+
* Converts the current string to a naive sentence case.
192+
*/
193+
public function sentence(): static
194+
{
195+
return $this->createOrModify(to_sentence_case($this->value));
196+
}
197+
198+
/**
199+
* Returns an array of words from the current string.
200+
*/
201+
public function words(): ImmutableArray
202+
{
203+
return new ImmutableArray(to_words($this->value));
204+
}
205+
190206
/**
191207
* Transliterates the current string to ASCII. Invalid characters are replaced with their closest counterpart.
192208
*

src/Tempest/Support/src/Str/functions.php

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use ArrayAccess;
77
use Countable;
88
use Stringable;
9+
use Tempest\Support\Arr\ImmutableArray;
910
use Tempest\Support\Language;
1011
use voku\helper\ASCII;
1112

@@ -22,6 +23,19 @@ function to_title_case(Stringable|string $string): string
2223
return mb_convert_case((string) $string, mode: MB_CASE_TITLE, encoding: 'UTF-8');
2324
}
2425

26+
/**
27+
* Converts the given string to a naive sentence case. This doesn't detect proper nouns and proper adjectives that should stay capitalized.
28+
*/
29+
function to_sentence_case(Stringable|string $string): string
30+
{
31+
$words = array_map(
32+
callback: fn (string $string) => to_lower_case($string),
33+
array: to_words($string),
34+
);
35+
36+
return upper_first(implode(' ', $words));
37+
}
38+
2539
/**
2640
* Converts the given string to lower case.
2741
*/
@@ -51,17 +65,35 @@ function to_snake_case(Stringable|string $string, Stringable|string $delimiter =
5165
}
5266

5367
$string = preg_replace('/(.)(?=[A-Z])/u', '$1' . $delimiter, $string);
54-
$string = preg_replace(
55-
'![^' . preg_quote($delimiter) . '\pL\pN\s]+!u',
56-
$delimiter,
57-
mb_strtolower($string, 'UTF-8'),
58-
);
68+
$string = preg_replace('![^' . preg_quote($delimiter) . '\pL\pN\s]+!u', $delimiter, mb_strtolower($string, 'UTF-8'));
5969
$string = preg_replace('/\s+/u', $delimiter, $string);
6070
$string = trim($string, $delimiter);
6171

6272
return deduplicate($string, $delimiter);
6373
}
6474

75+
/**
76+
* Returns an array of words from the specified string.
77+
* This is more accurate than {@see str_word_count()}.
78+
*/
79+
function to_words(Stringable|string $string): array
80+
{
81+
// Remove 'words' that don't consist of alphanumerical characters or punctuation
82+
$words = trim(preg_replace("#[^(\w|\d|\'|\"|\.|\!|\?|;|,|\\|\/|\-|:|\&|@)]+#", ' ', (string) $string));
83+
// Remove one-letter 'words' that consist only of punctuation
84+
$words = trim(preg_replace("#\s*[(\'|\"|\.|\!|\?|;|,|\\|\/|\-|:|\&|@)]\s*#", ' ', $words));
85+
86+
return array_values(array_filter(explode(' ', namespace\deduplicate($words))));
87+
}
88+
89+
/**
90+
* Counts the number of words in the given string.
91+
*/
92+
function word_count(Stringable|string $string): int
93+
{
94+
return count(to_words($string));
95+
}
96+
6597
/**
6698
* Converts the given string to kebab case.
6799
*/

src/Tempest/Support/tests/Str/ManipulatesStringTest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,4 +700,27 @@ public function test_is_ascii(): void
700700
$this->assertFalse(str('helloü')->isAscii());
701701
$this->assertFalse(str('بسم الله')->isAscii());
702702
}
703+
704+
#[TestWith(['foo bar baz', ['foo', 'bar', 'baz']])]
705+
#[TestWith(['foo-bar-baz', ['foo', 'bar', 'baz']])]
706+
#[TestWith(['1foo_bar1', ['1foo_bar1']])]
707+
#[TestWith(['fooBar', ['fooBar']])]
708+
#[TestWith(['Jon Doe', ['Jon', 'Doe']])]
709+
#[TestWith(['-Jon Doe', ['Jon', 'Doe']])]
710+
#[TestWith(['_Jon_Doe', ['_Jon_Doe']])]
711+
public function test_words(string $input, array $output): void
712+
{
713+
$this->assertEquals($output, str($input)->words()->toArray());
714+
}
715+
716+
#[TestWith(['foo bar baz', 'Foo bar baz'])]
717+
#[TestWith(['foo-bar-baz', 'Foo bar baz'])]
718+
#[TestWith(['Foo Bar', 'Foo bar'])]
719+
#[TestWith(['1foo_bar1', '1foo_bar1'])]
720+
#[TestWith(['getting-started', 'Getting started'])]
721+
#[TestWith(['Getting Started', 'Getting started'])]
722+
public function test_sentence(string $input, string $output): void
723+
{
724+
$this->assertEquals($output, str($input)->sentence()->toString());
725+
}
703726
}

0 commit comments

Comments
 (0)