Skip to content

Commit 74c6f83

Browse files
committed
Level 9
1 parent ca8ddaa commit 74c6f83

File tree

9 files changed

+318
-225
lines changed

9 files changed

+318
-225
lines changed

lib/cli/Arguments.php

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,25 @@ class Arguments implements \ArrayAccess {
5050
public function __construct($options = array()) {
5151
$options += array(
5252
'strict' => false,
53-
'input' => array_slice($_SERVER['argv'], 1)
53+
'input' => isset( $_SERVER['argv'] ) && is_array( $_SERVER['argv'] ) ? array_slice( $_SERVER['argv'], 1 ) : array(),
5454
);
5555

56-
$this->_input = $options['input'];
57-
$this->setStrict($options['strict']);
56+
$input = $options['input'];
57+
if ( ! is_array( $input ) ) {
58+
$input = array();
59+
}
60+
$this->_input = array_map( function( $item ) { return is_scalar( $item ) ? (string) $item : ''; }, $input );
61+
$this->setStrict( ! empty( $options['strict'] ) );
5862

59-
if (isset($options['flags'])) {
60-
$this->addFlags($options['flags']);
63+
if ( isset( $options['flags'] ) && is_array( $options['flags'] ) ) {
64+
/** @var array<string, array<string, mixed>|string> $flags */
65+
$flags = $options['flags'];
66+
$this->addFlags( $flags );
6167
}
62-
if (isset($options['options'])) {
63-
$this->addOptions($options['options']);
68+
if ( isset( $options['options'] ) && is_array( $options['options'] ) ) {
69+
/** @var array<string, array<string, mixed>|string> $opts */
70+
$opts = $options['options'];
71+
$this->addOptions( $opts );
6472
}
6573
}
6674

@@ -110,6 +118,10 @@ public function offsetExists($offset) {
110118
$offset = $offset->key;
111119
}
112120

121+
if ( ! is_string( $offset ) && ! is_int( $offset ) ) {
122+
return false;
123+
}
124+
113125
return array_key_exists($offset, $this->_parsed ?? []);
114126
}
115127

@@ -125,6 +137,10 @@ public function offsetGet($offset) {
125137
$offset = $offset->key;
126138
}
127139

140+
if ( ! is_string( $offset ) && ! is_int( $offset ) ) {
141+
return null;
142+
}
143+
128144
if (isset($this->_parsed[$offset])) {
129145
return $this->_parsed[$offset];
130146
}
@@ -144,6 +160,11 @@ public function offsetSet($offset, $value) {
144160
$offset = $offset->key;
145161
}
146162

163+
if ( ! is_string( $offset ) && ! is_int( $offset ) ) {
164+
return;
165+
}
166+
167+
$offset = (string) $offset;
147168
$this->_parsed[$offset] = $value;
148169
}
149170

@@ -158,6 +179,10 @@ public function offsetUnset($offset) {
158179
$offset = $offset->key;
159180
}
160181

182+
if ( ! is_string( $offset ) && ! is_int( $offset ) ) {
183+
return;
184+
}
185+
161186
unset($this->_parsed[$offset]);
162187
}
163188

@@ -180,6 +205,11 @@ public function addFlag($flag, $settings = array()) {
180205
$settings['aliases'] = $flag;
181206
$flag = array_shift($settings['aliases']);
182207
}
208+
if ( is_scalar( $flag ) ) {
209+
$flag = (string) $flag;
210+
} else {
211+
$flag = '';
212+
}
183213
if (isset($this->_flags[$flag])) {
184214
$this->_warn('flag already exists: ' . $flag);
185215
return $this;
@@ -235,6 +265,11 @@ public function addOption($option, $settings = array()) {
235265
$settings['aliases'] = $option;
236266
$option = array_shift($settings['aliases']);
237267
}
268+
if ( is_scalar( $option ) ) {
269+
$option = (string) $option;
270+
} else {
271+
$option = '';
272+
}
238273
if (isset($this->_options[$option])) {
239274
$this->_warn('option already exists: ' . $option);
240275
return $this;
@@ -308,6 +343,10 @@ public function getFlag($flag) {
308343
$flag = $flag->value();
309344
}
310345

346+
if ( ! is_string( $flag ) && ! is_int( $flag ) ) {
347+
return null;
348+
}
349+
311350
if (isset($this->_flags[$flag])) {
312351
return $this->_flags[$flag];
313352
}
@@ -381,6 +420,10 @@ public function getOption($option) {
381420
$option = $option->value();
382421
}
383422

423+
if ( ! is_string( $option ) && ! is_int( $option ) ) {
424+
return null;
425+
}
426+
384427
if (isset($this->_options[$option])) {
385428
return $this->_options[$option];
386429
}
@@ -454,7 +497,8 @@ public function parse() {
454497
continue;
455498
}
456499

457-
array_push($this->_invalid, $argument->raw());
500+
$raw = $argument->raw();
501+
array_push($this->_invalid, is_scalar($raw) ? (string) $raw : '');
458502
}
459503
}
460504

@@ -509,7 +553,8 @@ private function _parseFlag($argument) {
509553
$this[$argument->key] = 0;
510554
}
511555

512-
$this[$argument->key] += 1;
556+
$current = $this[$argument->key];
557+
$this[$argument->key] = (is_int($current) ? $current : 0) + 1;
513558
} else {
514559
$this[$argument->key] = true;
515560
}

lib/cli/Streams.php

Lines changed: 67 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ static function _call( $func, $args ) {
2929
*
3030
* @return bool
3131
*/
32-
static public function isTty() {
33-
if ( function_exists('stream_isatty') ) {
34-
return stream_isatty(static::$out);
32+
public static function isTty() {
33+
if ( function_exists( 'stream_isatty' ) ) {
34+
return stream_isatty( static::$out );
3535
} else {
36-
return (function_exists('posix_isatty') && posix_isatty(static::$out));
36+
return ( function_exists( 'posix_isatty' ) && posix_isatty( static::$out ) );
3737
}
3838
}
3939

@@ -48,31 +48,32 @@ static public function isTty() {
4848
* @return string The rendered string.
4949
*/
5050
public static function render( $msg, ...$args ) {
51-
$args = func_get_args();
52-
5351
// No string replacement is needed
54-
if( count( $args ) == 1 || ( is_string( $args[1] ) && '' === $args[1] ) ) {
52+
if ( empty( $args ) || ( is_string( $args[0] ) && '' === $args[0] ) ) {
5553
return Colors::shouldColorize() ? Colors::colorize( $msg ) : $msg;
5654
}
5755

5856
// If the first argument is not an array just pass to sprintf
59-
if( !is_array( $args[1] ) ) {
57+
if ( ! is_array( $args[0] ) ) {
6058
// Normalize color tokens before sprintf: colorize or strip them so no raw %tokens reach sprintf
6159
if ( Colors::shouldColorize() ) {
62-
$args[0] = Colors::colorize( $args[0] );
60+
$msg = Colors::colorize( $msg );
6361
} else {
64-
$args[0] = Colors::decolorize( $args[0] );
62+
$msg = Colors::decolorize( $msg );
6563
}
6664

6765
// Escape percent characters for sprintf
68-
$args[0] = preg_replace('/(%([^\w]|$))/', "%$1", $args[0]);
66+
$msg = (string) preg_replace( '/(%([^\w]|$))/', '%$1', $msg );
6967

70-
return call_user_func_array( 'sprintf', $args );
68+
$sprintf_args = array_merge( array( $msg ), $args );
69+
/** @var string $rendered */
70+
$rendered = call_user_func_array( 'sprintf', $sprintf_args );
71+
return $rendered;
7172
}
7273

7374
// Here we do named replacement so formatting strings are more understandable
74-
foreach( $args[1] as $key => $value ) {
75-
$msg = str_replace( '{:' . $key . '}', $value, $msg );
75+
foreach ( $args[0] as $key => $value ) {
76+
$msg = str_replace( '{:' . $key . '}', is_scalar( $value ) ? (string) $value : '', $msg );
7677
}
7778
return Colors::shouldColorize() ? Colors::colorize( $msg ) : $msg;
7879
}
@@ -87,7 +88,8 @@ public static function render( $msg, ...$args ) {
8788
* @see \cli\render()
8889
*/
8990
public static function out( $msg, ...$args ) {
90-
fwrite( static::$out, self::_call( 'render', func_get_args() ) );
91+
$rendered = self::_call( 'render', func_get_args() );
92+
fwrite( static::$out, is_scalar( $rendered ) ? (string) $rendered : '' );
9193
}
9294

9395
/**
@@ -99,7 +101,8 @@ public static function out( $msg, ...$args ) {
99101
* @see cli\out()
100102
*/
101103
public static function out_padded( $msg, ...$args ) {
102-
$msg = self::_call( 'render', func_get_args() );
104+
$rendered = self::_call( 'render', func_get_args() );
105+
$msg = is_scalar( $rendered ) ? (string) $rendered : '';
103106
self::out( str_pad( $msg, \cli\Shell::columns() ) );
104107
}
105108

@@ -113,8 +116,8 @@ public static function out_padded( $msg, ...$args ) {
113116
*/
114117
public static function line( $msg = '' ) {
115118
// func_get_args is empty if no args are passed even with the default above.
116-
$args = array_merge( func_get_args(), array( '' ) );
117-
$args[0] .= "\n";
119+
$args = array_merge( func_get_args(), array( '' ) );
120+
$args[0] = ( is_scalar( $args[0] ) ? (string) $args[0] : '' ) . "\n";
118121

119122
self::_call( 'out', $args );
120123
}
@@ -130,9 +133,10 @@ public static function line( $msg = '' ) {
130133
*/
131134
public static function err( $msg = '', ...$args ) {
132135
// func_get_args is empty if no args are passed even with the default above.
133-
$args = array_merge( func_get_args(), array( '' ) );
134-
$args[0] .= "\n";
135-
fwrite( static::$err, self::_call( 'render', $args ) );
136+
$args = array_merge( func_get_args(), array( '' ) );
137+
$args[0] = ( is_scalar( $args[0] ) ? (string) $args[0] : '' ) . "\n";
138+
$rendered = self::_call( 'render', $args );
139+
fwrite( static::$err, is_scalar( $rendered ) ? (string) $rendered : '' );
136140
}
137141

138142
/**
@@ -147,10 +151,11 @@ public static function err( $msg = '', ...$args ) {
147151
* @throws \Exception Thrown if ctrl-D (EOT) is sent as input.
148152
*/
149153
public static function input( $format = null, $hide = false ) {
150-
if ( $hide )
154+
if ( $hide ) {
151155
Shell::hide();
156+
}
152157

153-
if( $format ) {
158+
if ( $format ) {
154159
fscanf( static::$in, $format . "\n", $line );
155160
} else {
156161
$line = fgets( static::$in );
@@ -161,7 +166,7 @@ public static function input( $format = null, $hide = false ) {
161166
echo "\n";
162167
}
163168

164-
if( $line === false ) {
169+
if ( $line === false ) {
165170
throw new \Exception( 'Caught ^D during input' );
166171
}
167172

@@ -181,18 +186,18 @@ public static function input( $format = null, $hide = false ) {
181186
* @see cli\input()
182187
*/
183188
public static function prompt( $question, $default = false, $marker = ': ', $hide = false ) {
184-
if( $default && strpos( $question, '[' ) === false ) {
189+
if ( $default && strpos( $question, '[' ) === false ) {
185190
$question .= ' [' . $default . ']';
186191
}
187192

188-
while( true ) {
193+
while ( true ) {
189194
self::out( $question . $marker );
190195
$line = self::input( null, $hide );
191196

192197
if ( trim( $line ) !== '' ) {
193198
return $line;
194199
}
195-
if( $default !== false ) {
200+
if ( $default !== false ) {
196201
return (string) $default;
197202
}
198203
}
@@ -209,7 +214,7 @@ public static function prompt( $question, $default = false, $marker = ': ', $hid
209214
* @see cli\prompt()
210215
*/
211216
public static function choose( $question, $choice = 'yn', $default = 'n' ) {
212-
if( !is_string( $choice ) ) {
217+
if ( ! is_string( $choice ) ) {
213218
$choice = join( '', $choice );
214219
}
215220

@@ -222,13 +227,13 @@ public static function choose( $question, $choice = 'yn', $default = 'n' ) {
222227
// Separate each choice with a forward-slash
223228
$choices = trim( join( '/', str_split( $choice ) ), '/' );
224229

225-
while( true ) {
230+
while ( true ) {
226231
$line = self::prompt( sprintf( '%s? [%s]', $question, $choices ), $default ?? false, '' );
227232

228-
if( stripos( $choice, $line ) !== false ) {
233+
if ( stripos( $choice, $line ) !== false ) {
229234
return strtolower( $line );
230235
}
231-
if( !empty( $default ) ) {
236+
if ( ! empty( $default ) ) {
232237
return strtolower( $default );
233238
}
234239
}
@@ -250,29 +255,42 @@ public static function choose( $question, $choice = 'yn', $default = 'n' ) {
250255
public static function menu( $items, $default = null, $title = 'Choose an item' ) {
251256
$map = array_values( $items );
252257

253-
if( $default && strpos( $title, '[' ) === false && isset( $items[$default] ) ) {
254-
$title .= ' [' . $items[$default] . ']';
258+
if ( $default && strpos( $title, '[' ) === false && isset( $items[ $default ] ) ) {
259+
$default_item = $items[ $default ];
260+
$default_str = '';
261+
if ( is_scalar( $default_item ) ) {
262+
$default_str = (string) $default_item;
263+
} elseif ( is_object( $default_item ) && method_exists( $default_item, '__toString' ) ) {
264+
$default_str = (string) $default_item;
265+
}
266+
$title .= ' [' . $default_str . ']';
255267
}
256268

257-
foreach( $map as $idx => $item ) {
258-
self::line( ' %d. %s', $idx + 1, (string)$item );
269+
foreach ( $map as $idx => $item ) {
270+
$item_str = '';
271+
if ( is_scalar( $item ) ) {
272+
$item_str = (string) $item;
273+
} elseif ( is_object( $item ) && method_exists( $item, '__toString' ) ) {
274+
$item_str = (string) $item;
275+
}
276+
self::line( ' %d. %s', $idx + 1, $item_str );
259277
}
260278
self::line();
261279

262-
while( true ) {
280+
while ( true ) {
263281
fwrite( static::$out, sprintf( '%s: ', $title ) );
264282
$line = self::input();
265283

266-
if( is_numeric( $line ) ) {
267-
$line--;
268-
if( isset( $map[$line] ) ) {
269-
return (string) array_search( $map[$line], $items );
284+
if ( is_numeric( $line ) ) {
285+
--$line;
286+
if ( isset( $map[ $line ] ) ) {
287+
return (string) array_search( $map[ $line ], $items );
270288
}
271289

272-
if( $line < 0 || $line >= count( $map ) ) {
290+
if ( $line < 0 || $line >= count( $map ) ) {
273291
self::err( 'Invalid menu selection: out of range' );
274292
}
275-
} else if( isset( $default ) ) {
293+
} elseif ( isset( $default ) ) {
276294
return $default;
277295
}
278296
}
@@ -295,15 +313,16 @@ public static function menu( $items, $default = null, $title = 'Choose an item'
295313
* @throws \Exception Thrown if $stream is not a resource of the 'stream' type.
296314
*/
297315
public static function setStream( $whichStream, $stream ) {
298-
if( !is_resource( $stream ) || get_resource_type( $stream ) !== 'stream' ) {
316+
if ( ! is_resource( $stream ) || get_resource_type( $stream ) !== 'stream' ) {
299317
throw new \Exception( 'Invalid resource type!' );
300318
}
301-
if( property_exists( __CLASS__, $whichStream ) ) {
319+
if ( property_exists( __CLASS__, $whichStream ) ) {
302320
static::${$whichStream} = $stream;
303321
}
304-
register_shutdown_function( function() use ($stream) {
305-
fclose( $stream );
306-
} );
322+
register_shutdown_function(
323+
function () use ( $stream ) {
324+
fclose( $stream );
325+
}
326+
);
307327
}
308-
309328
}

0 commit comments

Comments
 (0)