Skip to content

Commit fc2afa9

Browse files
committed
Add str_increment and str_decrement functions
1 parent 4051889 commit fc2afa9

File tree

3 files changed

+240
-0
lines changed

3 files changed

+240
-0
lines changed

src/Php83/Php83.php

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
/**
1515
* @author Ion Bazan <ion.bazan@gmail.com>
16+
* @author Pierre Ambroise <pierre27.ambroise@gmail.com>
1617
*
1718
* @internal
1819
*/
@@ -82,4 +83,115 @@ public static function mb_str_pad(string $string, int $length, string $pad_strin
8283
return mb_substr(str_repeat($pad_string, $leftPaddingLength), 0, $leftPaddingLength, $encoding).$string.mb_substr(str_repeat($pad_string, $rightPaddingLength), 0, $rightPaddingLength, $encoding);
8384
}
8485
}
86+
87+
public static function str_increment(string $string): string
88+
{
89+
if ('' === $string) {
90+
throw new \ValueError('str_increment(): Argument #1 ($string) cannot be empty');
91+
}
92+
93+
if (!\preg_match("/^[a-zA-Z0-9]+$/", $string)) {
94+
throw new \ValueError('str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters');
95+
}
96+
97+
if (\is_numeric($string)) {
98+
$offset = stripos($string, 'e');
99+
if ($offset !== false) {
100+
$char = $string[$offset];
101+
$char++;
102+
$string[$offset] = $char;
103+
$string++;
104+
105+
switch ($string[$offset]) {
106+
case 'f':
107+
$string[$offset] = 'e';
108+
break;
109+
case 'F':
110+
$string[$offset] = 'E';
111+
break;
112+
case 'g':
113+
$string[$offset] = 'f';
114+
break;
115+
case 'G':
116+
$string[$offset] = 'F';
117+
break;
118+
}
119+
120+
return $string;
121+
}
122+
}
123+
124+
return ++$string;
125+
}
126+
127+
public static function str_decrement(string $string): string
128+
{
129+
if ('' === $string) {
130+
throw new \ValueError('str_decrement(): Argument #1 ($string) cannot be empty');
131+
}
132+
133+
if (!\preg_match("/^[a-zA-Z0-9]+$/", $string)) {
134+
throw new \ValueError('str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters');
135+
}
136+
137+
if (\preg_match('/\A(?:0[aA0]?|[aA])\z/', $string)) {
138+
throw new \ValueError(sprintf('str_decrement(): Argument #1 ($string) "%s" is out of decrement range', $string));
139+
}
140+
141+
if (!\in_array(substr($string, -1), ['A', 'a', '0'], true)) {
142+
return join('', array_slice(str_split($string), 0, -1)) . chr(ord(substr($string, -1)) - 1);
143+
}
144+
145+
$carry = '';
146+
$decremented = '';
147+
148+
for ($i = strlen($string) - 1; $i >= 0; $i--) {
149+
$char = $string[$i];
150+
151+
switch ($char) {
152+
case 'A':
153+
if ('' !== $carry) {
154+
$decremented .= $carry;
155+
$carry = '';
156+
}
157+
$carry = 'Z';
158+
159+
break;
160+
case 'a':
161+
if ('' !== $carry) {
162+
$decremented .= $carry;
163+
$carry = '';
164+
}
165+
$carry = 'z';
166+
167+
break;
168+
case '0':
169+
if ('' !== $carry) {
170+
$decremented .= $carry;
171+
$carry = '';
172+
}
173+
$carry = '9';
174+
175+
break;
176+
case '1':
177+
if ('' !== $carry) {
178+
$decremented .= $carry;
179+
$carry = '';
180+
}
181+
182+
break;
183+
default:
184+
if ('' !== $carry) {
185+
$decremented .= $carry;
186+
$carry = '';
187+
}
188+
189+
if (!\in_array($char, ['A', 'a', '0'], true)) {
190+
$decremented .= chr(ord($char) - 1);
191+
}
192+
}
193+
}
194+
195+
return strrev($decremented);
196+
}
85197
}

src/Php83/bootstrap.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $
2727
function stream_context_set_options($context, array $options): bool { return stream_context_set_option($context, $options); }
2828
}
2929

30+
if (!function_exists('str_increment')) {
31+
function str_increment(string $string): string { return p\Php83::str_increment($string); }
32+
}
33+
34+
if (!function_exists('str_decrement')) {
35+
function str_decrement(string $string): string { return p\Php83::str_decrement($string); }
36+
}
37+
3038
if (\PHP_VERSION_ID >= 80100) {
3139
return require __DIR__.'/bootstrap81.php';
3240
}

tests/Php83/Php83Test.php

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,4 +202,124 @@ public function testDateTimeExceptionClassesExist()
202202
$this->assertTrue(class_exists(\DateMalformedIntervalStringException::class));
203203
$this->assertTrue(class_exists(\DateMalformedPeriodStringException::class));
204204
}
205+
206+
/**
207+
* @covers \Symfony\Polyfill\Php83\Php83::str_increment
208+
*
209+
* @dataProvider strIncrementProvider
210+
*/
211+
public function testStrIncrement(string $result, string $string)
212+
{
213+
$this->assertSame($result, str_increment($string));
214+
}
215+
216+
/**
217+
* @covers \Symfony\Polyfill\Php83\Php83::str_decrement
218+
*
219+
* @dataProvider strDecrementProvider
220+
*/
221+
public function testStrDecrement(string $result, string $string)
222+
{
223+
$this->assertSame($result, str_decrement($string));
224+
}
225+
226+
public static function strIncrementProvider(): iterable
227+
{
228+
yield ['ABD', 'ABC'];
229+
yield ['EB', 'EA'];
230+
yield ['AAA', 'ZZ'];
231+
yield ['Ba', 'Az'];
232+
yield ['bA', 'aZ'];
233+
yield ['B0', 'A9'];
234+
yield ['b0', 'a9'];
235+
yield ['AAa', 'Zz'];
236+
yield ['aaA', 'zZ'];
237+
yield ['10a', '9z'];
238+
yield ['10A', '9Z'];
239+
yield ['5e7', '5e6'];
240+
yield ['e', 'd'];
241+
yield ['E', 'D'];
242+
yield ['5', '4'];
243+
}
244+
245+
public static function strDecrementProvider(): iterable
246+
{
247+
yield ['Ay', 'Az'];
248+
yield ['aY', 'aZ'];
249+
yield ['A8', 'A9'];
250+
yield ['a8', 'a9'];
251+
yield ['Yz', 'Za'];
252+
yield ['yZ', 'zA'];
253+
yield ['Y9', 'Z0'];
254+
yield ['y9', 'z0'];
255+
yield ['Z', 'aA'];
256+
yield ['9', 'A0'];
257+
yield ['9', 'a0'];
258+
yield ['9', '10'];
259+
yield ['Z', '1A'];
260+
yield ['z', '1a'];
261+
yield ['9z', '10a'];
262+
yield ['5e5', '5e6'];
263+
yield ['C', 'D'];
264+
yield ['c', 'd'];
265+
yield ['3', '4'];
266+
}
267+
268+
/**
269+
* @covers \Symfony\Polyfill\Php83\Php83::str_increment
270+
*
271+
* @dataProvider strInvalidIncrementProvider
272+
*/
273+
public function testInvalidStrIncrement(string $errorMessage, string $string)
274+
{
275+
$this->expectException(\ValueError::class);
276+
$this->expectExceptionMessage($errorMessage);
277+
278+
str_increment($string);
279+
}
280+
281+
282+
public static function strInvalidIncrementProvider(): iterable
283+
{
284+
yield ['str_increment(): Argument #1 ($string) cannot be empty', ""];
285+
yield ['str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters', "-cc"];
286+
yield ['str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters', "Z "];
287+
yield ['str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters', " Z"];
288+
yield ['str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters', "é"];
289+
yield ['str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters', '我喜歡雞肉'];
290+
yield ['str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters', 'α'];
291+
yield ['str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters', 'ω'];
292+
yield ['str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters', 'Α'];
293+
yield ['str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters', 'Ω'];
294+
yield ['str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters', 'foo1.txt'];
295+
yield ['str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters', '1f.5'];
296+
yield ['str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters', 'foo.1.txt'];
297+
yield ['str_increment(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters', '1.f.5'];
298+
}
299+
300+
301+
/**
302+
* @covers \Symfony\Polyfill\Php83\Php83::str_decrement
303+
*
304+
* @dataProvider strInvalidDecrementProvider
305+
*/
306+
public function testInvalidStrDecrement(string $errorMessage, string $string)
307+
{
308+
$this->expectException(\ValueError::class);
309+
$this->expectExceptionMessage($errorMessage);
310+
311+
str_decrement($string);
312+
}
313+
314+
public static function strInvalidDecrementProvider(): iterable
315+
{
316+
yield ['str_decrement(): Argument #1 ($string) cannot be empty', ''];
317+
yield ['str_decrement(): Argument #1 ($string) must be composed only of alphanumeric ASCII characters', '我喜歡雞肉'];
318+
yield ['str_decrement(): Argument #1 ($string) "0" is out of decrement range', '0'];
319+
yield ['str_decrement(): Argument #1 ($string) "a" is out of decrement range', 'a'];
320+
yield ['str_decrement(): Argument #1 ($string) "A" is out of decrement range', 'A'];
321+
yield ['str_decrement(): Argument #1 ($string) "00" is out of decrement range', '00'];
322+
yield ['str_decrement(): Argument #1 ($string) "0a" is out of decrement range', '0a'];
323+
yield ['str_decrement(): Argument #1 ($string) "0A" is out of decrement range', '0A'];
324+
}
205325
}

0 commit comments

Comments
 (0)