Skip to content

Commit 18c919c

Browse files
committed
new: add new helper class for flags
1 parent 6db64b5 commit 18c919c

File tree

3 files changed

+187
-106
lines changed

3 files changed

+187
-106
lines changed

src/Flags.php

Lines changed: 37 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,17 @@
99

1010
namespace Toolkit\Cli;
1111

12+
use Toolkit\Cli\Helper\FlagHelper;
1213
use Toolkit\Cli\Util\LineParser;
1314
use function array_flip;
1415
use function array_merge;
1516
use function current;
1617
use function escapeshellarg;
1718
use function explode;
18-
use function is_bool;
1919
use function is_int;
20-
use function is_numeric;
2120
use function next;
2221
use function preg_match;
2322
use function str_split;
24-
use function stripos;
2523
use function strpos;
2624
use function substr;
2725
use function trim;
@@ -33,11 +31,6 @@
3331
*/
3432
class Flags
3533
{
36-
// These words will be as a Boolean value
37-
private const TRUE_WORDS = '|on|yes|true|';
38-
39-
private const FALSE_WORDS = '|off|no|false|';
40-
4134
/**
4235
* @param array $argv
4336
*
@@ -134,26 +127,26 @@ public static function parseArgv(array $params, array $config = []): array
134127
// Special short style
135128
// posix: -abc will expand: -a -b -c
136129
// unix: -abc will expand: -a=bc
137-
'shortStyle' => 'posix',
130+
'shortStyle' => 'posix',
138131
], $config);
139132

140133
$args = $sOpts = $lOpts = [];
141134
// config
142135
$boolOpts = array_flip((array)$config['boolOpts']);
143136
$arrayOpts = array_flip((array)$config['arrayOpts']);
144137

145-
// each() will deprecated at 7.2. so,there use current and next instead it.
146-
// while (list(,$p) = each($params)) {
138+
$optParseEnd = false;
147139
while (false !== ($p = current($params))) {
148140
next($params);
149141

150-
// empty string
151-
if ($p === '') {
142+
// option parse end, collect remaining arguments.
143+
if ($optParseEnd) {
144+
self::collectArgs($args, $p);
152145
continue;
153146
}
154147

155148
// is options and not equals '-' '--'
156-
if ($p[0] === '-' && '' !== trim($p, '-')) {
149+
if ($p && $p[0] === '-' && '' !== trim($p, '-')) {
157150
$value = true;
158151
$isLong = false;
159152
$option = substr($p, 1);
@@ -177,7 +170,7 @@ public static function parseArgv(array $params, array $config = []): array
177170
$nxt = current($params);
178171

179172
// next elem is value. fix: allow empty string ''
180-
if ($value === true && !isset($boolOpts[$option]) && self::nextIsValue($nxt)) {
173+
if ($value === true && !isset($boolOpts[$option]) && FlagHelper::isOptionValue($nxt)) {
181174
// list(,$val) = each($params);
182175
$value = $nxt;
183176
next($params);
@@ -190,7 +183,7 @@ public static function parseArgv(array $params, array $config = []): array
190183
continue;
191184
}
192185

193-
$value = self::filterBool($value);
186+
$value = FlagHelper::filterBool($value);
194187
$isArray = isset($arrayOpts[$option]);
195188

196189
if ($isLong) {
@@ -208,21 +201,16 @@ public static function parseArgv(array $params, array $config = []): array
208201
continue;
209202
}
210203

204+
// stop parse options:
205+
// - found '--' will stop parse options
206+
if ($p === '--') {
207+
$optParseEnd = true;
208+
continue;
209+
}
210+
211211
// parse arguments:
212212
// - param doesn't belong to any option, define it is args
213-
214-
// value specified inline (<arg>=<value>)
215-
if (strpos($p, '=') !== false) {
216-
[$name, $value] = explode('=', $p, 2);
217-
218-
if (self::isValidArgName($name)) {
219-
$args[$name] = self::filterBool($value);
220-
} else {
221-
$args[] = $p;
222-
}
223-
} else {
224-
$args[] = $p;
225-
}
213+
self::collectArgs($args, $p);
226214
}
227215

228216
if ($config['mergeOpts']) {
@@ -232,6 +220,26 @@ public static function parseArgv(array $params, array $config = []): array
232220
return [$args, $sOpts, $lOpts];
233221
}
234222

223+
/**
224+
* @param array $args
225+
* @param string $p
226+
*/
227+
private static function collectArgs(array &$args, string $p): void
228+
{
229+
// value specified inline (<arg>=<value>)
230+
if (strpos($p, '=') !== false) {
231+
[$name, $value] = explode('=', $p, 2);
232+
233+
if (FlagHelper::isValidName($name)) {
234+
$args[$name] = FlagHelper::filterBool($value);
235+
} else {
236+
$args[] = $p;
237+
}
238+
} else {
239+
$args[] = $p;
240+
}
241+
}
242+
235243
/**
236244
* parse custom array params
237245
* ```php
@@ -295,79 +303,6 @@ public static function parseString(string $string, array $config = []): array
295303
return self::parseArgv($flags, $config);
296304
}
297305

298-
/**
299-
* @param string|bool $val
300-
* @param bool $enable
301-
*
302-
* @return bool|int|mixed
303-
*/
304-
public static function filterBool($val, bool $enable = true)
305-
{
306-
if ($enable) {
307-
if (is_bool($val) || is_numeric($val)) {
308-
return $val;
309-
}
310-
311-
// check it is a bool value.
312-
if (false !== stripos(self::TRUE_WORDS, "|$val|")) {
313-
return true;
314-
}
315-
316-
if (false !== stripos(self::FALSE_WORDS, "|$val|")) {
317-
return false;
318-
}
319-
}
320-
321-
return $val;
322-
}
323-
324-
/**
325-
* check next is option value
326-
*
327-
* @param mixed $val
328-
*
329-
* @return bool
330-
*/
331-
public static function nextIsValue($val): bool
332-
{
333-
// current() fetch error, will return FALSE
334-
if ($val === false) {
335-
return false;
336-
}
337-
338-
// if is: '', 0
339-
if (!$val) {
340-
return true;
341-
}
342-
343-
// is not option name.
344-
if ($val[0] !== '-') {
345-
// ensure is option value.
346-
if (false === strpos($val, '=')) {
347-
return true;
348-
}
349-
350-
// is string value, but contains '='
351-
[$name,] = explode('=', $val, 2);
352-
353-
// named argument OR invlaid: 'some = string'
354-
return false === self::isValidArgName($name);
355-
}
356-
357-
// is option name.
358-
return false;
359-
}
360-
361-
/**
362-
* @param string $name
363-
*
364-
* @return bool
365-
*/
366-
public static function isValidArgName(string $name): bool
367-
{
368-
return preg_match('#^\w+$#', $name) === 1;
369-
}
370-
371306
/**
372307
* Escapes a token through escapeshellarg if it contains unsafe chars.
373308
*

src/Helper/FlagHelper.php

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<?php declare(strict_types=1);
2+
/**
3+
* This file is part of toolkit/cli-utils.
4+
*
5+
* @link https://github.com/php-toolkit/cli-utils
6+
* @author https://github.com/inhere
7+
* @license MIT
8+
*/
9+
10+
namespace Toolkit\Cli\Helper;
11+
12+
use function escapeshellarg;
13+
use function explode;
14+
use function is_bool;
15+
use function is_numeric;
16+
use function preg_match;
17+
use function stripos;
18+
use function strpos;
19+
20+
/**
21+
* class FlagHelper
22+
*/
23+
class FlagHelper
24+
{
25+
// These words will be as a Boolean value
26+
public const TRUE_WORDS = '|on|yes|true|';
27+
public const FALSE_WORDS = '|off|no|false|';
28+
29+
/**
30+
* @param string $val
31+
*
32+
* @return bool
33+
*/
34+
public static function str2bool(string $val): bool
35+
{
36+
// check it is a bool value.
37+
if (false !== stripos(self::TRUE_WORDS, "|$val|")) {
38+
return true;
39+
}
40+
41+
if (false !== stripos(self::FALSE_WORDS, "|$val|")) {
42+
return false;
43+
}
44+
45+
// TODO throws error
46+
return false;
47+
}
48+
49+
/**
50+
* @param string|bool|int|mixed $val
51+
*
52+
* @return bool|int|mixed
53+
*/
54+
public static function filterBool($val)
55+
{
56+
if (is_bool($val) || is_numeric($val)) {
57+
return $val;
58+
}
59+
60+
// check it is a bool value.
61+
if (false !== stripos(self::TRUE_WORDS, "|$val|")) {
62+
return true;
63+
}
64+
65+
if (false !== stripos(self::FALSE_WORDS, "|$val|")) {
66+
return false;
67+
}
68+
69+
return $val;
70+
}
71+
72+
/**
73+
* check input is valid option value
74+
*
75+
* @param mixed $val
76+
*
77+
* @return bool
78+
*/
79+
public static function isOptionValue($val): bool
80+
{
81+
if ($val === false) {
82+
return false;
83+
}
84+
85+
// if is: '', 0
86+
if (!$val) {
87+
return true;
88+
}
89+
90+
// is not option name.
91+
if ($val[0] !== '-') {
92+
// ensure is option value.
93+
if (false === strpos($val, '=')) {
94+
return true;
95+
}
96+
97+
// is string value, but contains '='
98+
[$name,] = explode('=', $val, 2);
99+
100+
// named argument OR invalid: 'some = string'
101+
return false === self::isValidName($name);
102+
}
103+
104+
// is option name.
105+
return false;
106+
}
107+
108+
/**
109+
* @param string $name
110+
*
111+
* @return bool
112+
*/
113+
public static function isValidName(string $name): bool
114+
{
115+
return preg_match('#^\w+$#', $name) === 1;
116+
}
117+
118+
/**
119+
* Escapes a token through escape shell arg if it contains unsafe chars.
120+
*
121+
* @param string $token
122+
*
123+
* @return string
124+
*/
125+
public static function escapeToken(string $token): string
126+
{
127+
return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token);
128+
}
129+
}

test/FlagsTest.php

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
use PHPUnit\Framework\TestCase;
1313
use Toolkit\Cli\Flags;
14+
use Toolkit\Cli\Helper\FlagHelper;
1415
use function explode;
1516

1617
/**
@@ -54,7 +55,7 @@ public function testParseInvalidArgName(): void
5455
$this->assertSame('http://some.com/path/to/merge_requests/new?utf8=%E2%9C%93&merge_request%5Bsource_project_id%5D=319', $args[1]);
5556
}
5657

57-
public function testisParseWithSpace(): void
58+
public function testParseWithSpace(): void
5859
{
5960
[$args, , ] = Flags::parseArgv([
6061
'cmd',
@@ -65,9 +66,25 @@ public function testisParseWithSpace(): void
6566
$this->assertSame(' -', $args[1]);
6667
}
6768

68-
public function testisValidArgName(): void
69+
public function testStopParseOnTwoHl(): void
6970
{
70-
$this->assertTrue(Flags::isValidArgName('arg0'));
71-
$this->assertFalse(Flags::isValidArgName('/path/to'));
71+
[$args, , ] = Flags::parseArgv([
72+
'-n',
73+
'inhere',
74+
'--',
75+
'--age',
76+
'99',
77+
'cmd',
78+
' -'
79+
]);
80+
81+
$this->assertSame('--age', $args[0]);
82+
$this->assertCount(4, $args);
83+
}
84+
85+
public function testIsValidArgName(): void
86+
{
87+
$this->assertTrue(FlagHelper::isValidName('arg0'));
88+
$this->assertFalse(FlagHelper::isValidName('/path/to'));
7289
}
7390
}

0 commit comments

Comments
 (0)