@@ -24,71 +24,97 @@ class Interact
2424 const NL_TAB = "\n " ;// new line + tab
2525
2626/////////////////////////////////////////////////////////////////
27- /// Interactive
27+ /// Interactive method (select/confirm/question/loopAsk)
2828/////////////////////////////////////////////////////////////////
2929
3030 /**
3131 * 多选一
32- * @param string $question 说明
33- * @param mixed $option 选项数据
34- * @param bool $allowExit 有退出选项 默认 true
32+ * @param string $description 说明
33+ * @param mixed $options 选项数据
34+ * e.g
35+ * [
36+ * // option => value
37+ * '1' => 'chengdu',
38+ * '2' => 'beijing'
39+ * ]
40+ * @param mixed $default 默认选项
41+ * @param bool $allowExit 有退出选项 默认 true
3542 * @return string
3643 */
37- public static function choice ( $ question , $ option , $ allowExit =true )
44+ public static function select ( $ description , $ options , $ default = null , $ allowExit =true )
3845 {
39- self ::write (" <comment> $ question</comment> " );
46+ self ::choice ($ description , $ options , $ default , $ allowExit );
47+ }
48+ public static function choice ($ description , $ options , $ default = null , $ allowExit =true )
49+ {
50+ if ( !$ description = trim ($ description ) ) {
51+ self ::error ('Please provide a description text! ' , 1 );
52+ }
53+
54+ self ::write (" <comment> $ description</comment> " );
4055
41- $ option = is_array ($ option ) ? $ option : explode (', ' , $ option );
42- // no set key
43- $ isNumeric = isset ($ option [0 ]);
4456 $ keys = [];
4557 $ optStr = '' ;
58+ $ options = is_array ($ options ) ? $ options : explode (', ' , $ options );
4659
47- foreach ($ option as $ key => $ value ) {
48- $ keys [] = $ isNumeric ? ++$ key : $ key ;
60+ // If defaut option is error
61+ if ( null === $ default && !isset ($ options [$ default ]) ) {
62+ self ::error ("The default option [ {$ default }] don't exists. " , true );
63+ }
4964
50- $ optStr .= "\n $ key) $ value " ;
65+ foreach ($ options as $ key => $ value ) {
66+ $ keys [] = $ key ;
67+ $ optStr .= "\n <info> $ key</info>) $ value " ;
5168 }
5269
5370 if ($ allowExit ) {
5471 $ keys [] = 'q ' ;
5572 $ optStr .= "\n q) quit " ;
5673 }
5774
58- self ::write ($ optStr . "\n You choice : " );
59-
60- $ r = self ::readRow ();
75+ $ r = self ::read ($ optStr . "\n You choice : " );
6176
77+ // error, allow try again
6278 if ( !in_array ($ r , $ keys ) ) {
63- self ::write ("Warning! option <comment> $ r<comment>) don't exists! please entry again! : " );
79+ $ r = self ::read ("Warning! Option <info> $ r</info>) don't exists! Please entry again! : " );
80+ }
6481
65- $ r = self ::readRow ();
82+ // exit
83+ if ( $ r === 'q ' ) {
84+ self ::write ("\n Quit,ByeBye. " , true , true );
6685 }
6786
68- if ($ r === 'q ' || !in_array ($ r , $ keys ) ) {
69- exit ("\n\n Quit,ByeBye. \n" );
87+ // error
88+ if ( !in_array ($ r , $ keys ) ) {
89+ if ( null === $ default ) {
90+ self ::write ("\n Select error. Quit,ByeBye. " , true , true );
91+ }
92+
93+ $ r = $ default ;
7094 }
7195
7296 return $ r ;
7397 }
7498
7599 /**
76- * 确认, 发出信息要求确认;返回 true | false
77- * @param string $question 发出的信息
78- * @param bool $yes
100+ * 确认, 发出信息要求确认
101+ * @param string $question 发出的信息
102+ * @param bool $default Default value
79103 * @return bool
80104 */
81- public static function confirm ($ question , $ yes = true )
105+ public static function confirm ($ question , $ default = true )
82106 {
83- $ question = ucfirst (trim ($ question ));
84- $ defaultText = $ yes ? 'yes ' : 'no ' ;
107+ if ( !$ question = trim ($ question ) ) {
108+ self ::error ('Please provide a question text! ' , 1 );
109+ }
85110
86- $ message = " <comment> $ question</comment> \n Please confirm (yes|no) [default:<info> $ defaultText </info>]: " ;
87- self :: write ( $ message , false ) ;
111+ $ question = ucfirst ( $ question) ;
112+ $ defaultText = ( bool ) $ default ? ' yes ' : ' no ' ;
88113
89- $ answer = self ::readRow ();
114+ $ message = " <comment> $ question ?</comment> \n Please confirm (yes|no) [default:<info> $ defaultText</info>]: " ;
115+ $ answer = self ::read ($ message );
90116
91- return $ answer ? !strncasecmp ($ answer , 'y ' , 1 ) : (bool )$ yes ;
117+ return $ answer ? !strncasecmp ($ answer , 'y ' , 1 ) : (bool )$ default ;
92118 }
93119
94120 /**
@@ -231,7 +257,7 @@ public static function loopAsk($question, $default = null, \Closure $validator =
231257 }
232258
233259/////////////////////////////////////////////////////////////////
234- /// Output Message
260+ /// Output Format Message(title/section/helpPanel/panel/table)
235261/////////////////////////////////////////////////////////////////
236262
237263 /**
@@ -260,31 +286,140 @@ public static function section($msg, $width = 50, $char = '-')
260286 }
261287
262288 /**
263- * 多行信息展示
289+ * Show console help message
290+ * @param string $usage The usage message text. e.g 'command [options] [arguments]'
291+ * @param array $commands The command list
292+ * e.g
293+ * [
294+ * // command => description
295+ * 'start' => 'Start the app server',
296+ * ... ...
297+ * ]
298+ * @param array $options The option list
299+ * e.g
300+ * [
301+ * // option => description
302+ * '-d' => 'Run the server on daemonize.(default: <comment>false</comment>)',
303+ * '-h, --help' => 'Display this help message'
304+ * ... ...
305+ * ]
306+ * @param array $examples The command usage example. e.g 'php server.php {start|reload|restart|stop} [-d]'
307+ * @param string $description The description text. e.g 'Composer version 1.3.2'
308+ */
309+ public static function consoleHelp ($ usage , $ commands = [], $ options = [], $ examples = [], $ description = '' )
310+ {
311+ self ::helpPanel ($ usage , $ commands , $ options , $ examples , $ description );
312+ }
313+ public static function helpPanel ($ usage , $ commands = [], $ options = [], $ examples = [], $ description = '' )
314+ {
315+ // usage
316+ self ::write ("<comment>Usage</comment>: \n {$ usage }\n" );
317+
318+ // options list
319+ if ( $ options ) {
320+ // translate array to string
321+ if ( is_array ($ options )) {
322+ $ optionMaxWidth = ConsoleHelper::keyMaxWidth ($ options );
323+ $ options = ConsoleHelper::spliceKeyValue ($ options , $ optionMaxWidth );
324+ }
325+
326+ if ( is_string ($ options ) ) {
327+ self ::write ("<comment>Options</comment>: \n {$ options }\n" );
328+ }
329+ }
330+
331+ // command list
332+ if ( $ commands ) {
333+ // translate array to string
334+ if ( is_array ($ commands )) {
335+ $ commandMaxWidth = ConsoleHelper::keyMaxWidth ($ commands );
336+ $ commands = ConsoleHelper::spliceKeyValue ($ commands , $ commandMaxWidth );
337+ }
338+
339+ if ( is_string ($ commands ) ) {
340+ self ::write ("<comment>Commands</comment>: \n {$ commands }\n" );
341+ }
342+ }
343+
344+ // examples list
345+ if ( $ examples ) {
346+ $ examples = is_array ($ examples ) ? implode (PHP_EOL , $ examples ) : $ examples ;
347+ self ::write ("<comment>Examples</comment>: \n {$ examples }\n" );
348+ }
349+ }
350+
351+ /**
352+ * Show information data panel
264353 * @param mixed $data
265354 * @param string $title
266355 * @return void
267356 */
268- public static function panel (array $ data , $ title ='Info panel ' )
357+ public static function panel ($ data , $ title ='Info panel ' , $ char = ' * ' )
269358 {
270359 $ data = is_array ($ data ) ? array_filter ($ data ) : [trim ($ data )];
271- $ title = ucwords (trim ($ title ));
360+ $ title = trim ($ title );
361+
362+ $ panelData = []; // [ 'label' => 'value' ]
363+ $ labelMaxWidth = 0 ; // if label exists, label max width
364+ $ valueMaxWidth = 0 ; // value max width
272365
273366 self ::write ("\n " . sprintf (self ::STAR_LINE ,"<bold> $ title</bold> " ), false );
274367
275368 foreach ($ data as $ label => $ value ) {
276- $ line = ' * ' ;
369+ // label exists
370+ if ( !is_numeric ($ label ) ) {
371+ $ width = mb_strlen ($ label , 'UTF-8 ' );
372+ $ labelMaxWidth = $ width > $ labelMaxWidth ? $ width : $ labelMaxWidth ;
373+ }
374+
375+ // translate array to string
376+ if ( is_array ($ value ) ) {
377+ $ temp = '' ;
277378
278- if (!is_numeric ($ label )) {
279- $ line .= "$ label: " ;
379+ foreach ($ value as $ key => $ val ) {
380+ $ val = (string )$ val ;
381+ $ temp .= (!is_numeric ($ key ) ? "$ key: " : '' ) . "<info> $ value</info>, " ;
382+ }
383+
384+ $ value = rtrim ($ temp , ' , ' );
280385 }
281386
282- self ::write ("$ line <info> $ value</info> " );
387+ // get value width
388+ if ( is_string ($ value ) || is_numeric ($ value ) ) {
389+ $ value = trim ($ value );
390+ $ width = mb_strlen (strip_tags ($ value ), 'UTF-8 ' ); // must clear style tag
391+ $ valueMaxWidth = $ width > $ valueMaxWidth ? $ width : $ valueMaxWidth ;
392+ } else {
393+ throw new \Exception ('Panel data value only allow [array|string|number] ' );
394+ }
395+
396+ $ panelData [$ label ] = $ value ;
397+ }
398+
399+ $ panelWidth = $ labelMaxWidth + $ valueMaxWidth ;
400+
401+ // output title
402+ if ($ title ) {
403+ $ title = ucwords ($ title );
404+ $ titleLength = mb_strlen ($ title , 'UTF-8 ' );
405+ $ indentSpace = str_pad (' ' , ceil ($ panelWidth /2 ) - ceil ($ titleLength /2 ) + 2 *2 , ' ' );
406+ self ::write (" {$ indentSpace }<bold> {$ title }</bold> " );
283407 }
284408
285- $ star = $ title ? substr (self ::STAR_LINE , 0 , strlen ($ title )): '' ;
409+ // output panel top border
410+ $ border = str_pad ($ char , $ panelWidth + (2 *3 ), $ char );
411+ self ::write (' ' . $ border );
412+
413+ // output panel body
414+ $ panelStr = ConsoleHelper::spliceKeyValue ($ panelData , $ labelMaxWidth , ' | ' , " $ char " );
415+
416+ // already exists "\n"
417+ self ::write ($ panelStr , false );
418+
419+ // output panel bottom border
420+ self ::write (" $ border \n" );
286421
287- self :: write ( ' ' . sprintf ( self :: STAR_LINE , $ star ) );
422+ unset( $ data , $ panelData );
288423 }
289424
290425 /**
0 commit comments