diff --git a/README.md b/README.md index 962aa1a..1c58f7a 100644 --- a/README.md +++ b/README.md @@ -553,7 +553,7 @@ Show::table($data, 'a table', $opts); > 渲染效果请看下面的预览 -![table-show](images/table-show.png) +![table-show](docs/screenshots/table-show.png) ### 快速的渲染一个帮助信息面板 diff --git a/composer.json b/composer.json index 260a0f4..1e0cea8 100644 --- a/composer.json +++ b/composer.json @@ -26,6 +26,6 @@ } }, "suggest": { - "inhere/simple-print-tool": "Very lightweight data printing tools" + "symfony/process": "php process operation" } } diff --git a/images/table-show.png b/docs/screenshots/table-show.png similarity index 100% rename from images/table-show.png rename to docs/screenshots/table-show.png diff --git a/examples/Controllers/HomeController.php b/examples/Controllers/HomeController.php index 8f64b31..d9581fc 100644 --- a/examples/Controllers/HomeController.php +++ b/examples/Controllers/HomeController.php @@ -2,13 +2,14 @@ namespace Inhere\Console\Examples\Controllers; -use Inhere\Console\Components\AnsiCode; +use Inhere\Console\Components\Terminal; use Inhere\Console\Components\ArtFont; use Inhere\Console\Components\Download; use Inhere\Console\Controller; use Inhere\Console\IO\Input; use Inhere\Console\Utils\Helper; use Inhere\Console\Utils\Interact; +use Inhere\Console\Utils\ProgressBar; use Inhere\Console\Utils\Show; /** @@ -157,6 +158,8 @@ public function spinnerCommand() Show::spinner(); usleep(100); } + + Show::spinner('Done', true); } /** @@ -164,16 +167,18 @@ public function spinnerCommand() */ public function pendingCommand() { - $total = 5000; + $total = 8000; while ($total--) { - Show::spinner(); - usleep(100); + Show::pending(); + usleep(200); } + + Show::pending('Done', true); } /** - * a progress bar example show + * a progress bar example show, by Show::progressBar() * @options * --type the progress type, allow: bar,txt. txt * --done-char the done show char. = @@ -212,6 +217,26 @@ public function progressCommand($input) return 0; } + /** + * a progress bar example show, by class ProgressBar + * @throws \LogicException + */ + public function prgCommand() + { + $i = 0; + $total = 120; + $bar = new ProgressBar(); + $bar->start(120); + + while ($i <= $total) { + $bar->advance(); + usleep(50000); + $i++; + } + + $bar->finish(); + } + /** * output format message: title */ @@ -592,39 +617,36 @@ public function downCommand() } /** - * This is a demo for show cursor move on the screen + * This is a demo for show cursor move on the Terminal screen */ public function cursorCommand() { $this->write('hello, this in ' . __METHOD__); - - // $this->output->panel($_SERVER, 'Server information', ''); - $this->write('this is a message text.', false); sleep(1); - AnsiCode::make()->cursor(AnsiCode::CURSOR_BACKWARD, 6); + Terminal::make()->cursor(Terminal::CURSOR_BACKWARD, 6); sleep(1); - AnsiCode::make()->cursor(AnsiCode::CURSOR_FORWARD, 3); + Terminal::make()->cursor(Terminal::CURSOR_FORWARD, 3); sleep(1); - AnsiCode::make()->cursor(AnsiCode::CURSOR_BACKWARD, 2); + Terminal::make()->cursor(Terminal::CURSOR_BACKWARD, 2); sleep(2); - AnsiCode::make()->screen(AnsiCode::CLEAR_LINE, 3); + Terminal::make()->screen(Terminal::CLEAR_LINE, 3); $this->write('after 2s scroll down 3 row.'); sleep(2); - AnsiCode::make()->screen(AnsiCode::SCROLL_DOWN, 3); + Terminal::make()->screen(Terminal::SCROLL_DOWN, 3); $this->write('after 3s clear screen.'); sleep(3); - AnsiCode::make()->screen(AnsiCode::CLEAR); + Terminal::make()->screen(Terminal::CLEAR); } } diff --git a/examples/Controllers/ProcessController.php b/examples/Controllers/ProcessController.php new file mode 100644 index 0000000..7639fcd --- /dev/null +++ b/examples/Controllers/ProcessController.php @@ -0,0 +1,22 @@ +command(DemoCommand::class); $app->command('exam', function (Input $in, Output $out) { $cmd = $in->getCommand(); $out->info('hello, this is a test command: ' . $cmd); -}); +}, 'a description message'); $app->command('test', TestCommand::class, [ 'aliases' => ['t'] ]); -$app->command('prg', function () { - $i = 0; - $total = 120; - $bar = new ProgressBar(); - $bar->start(120); - - while ($i <= $total) { - $bar->advance(); - usleep(50000); - $i++; - } - - $bar->finish(); - -}, 'a description message'); +$app->controller(PharController::class); $app->controller('home', HomeController::class, [ 'aliases' => ['h'] ]); -$app->controller(PharController::class); +$app->controller(ProcessController::class, null, [ + 'prc' +]); diff --git a/src/Components/Notify/Pending.php b/src/Components/Notify/Pending.php index 0e393bf..314d3dc 100644 --- a/src/Components/Notify/Pending.php +++ b/src/Components/Notify/Pending.php @@ -16,5 +16,6 @@ */ class Pending extends NotifyMessage { - + /** @var int Speed value. allow 1 - 100 */ + private $speed = 20; } \ No newline at end of file diff --git a/src/Components/AnsiCode.php b/src/Components/Terminal.php similarity index 96% rename from src/Components/AnsiCode.php rename to src/Components/Terminal.php index 33180aa..06a62e6 100644 --- a/src/Components/AnsiCode.php +++ b/src/Components/Terminal.php @@ -11,14 +11,14 @@ use Inhere\Console\Utils\Show; /** - * Class AnsiCode terminal + * Class Terminal - terminal control by ansiCode * @package Inhere\Console\Components * * 2K 清除本行 * \x0D = \r = 13 回车,回到行首 * ESC = \x1B = 27 */ -final class AnsiCode +final class Terminal { const BEGIN_CHAR = "\033["; @@ -133,8 +133,8 @@ public static function make() * build ansi code string * * ``` - * AnsiCode::build(null, 'u'); // "\033[s" Saves the current cursor position - * AnsiCode::build(0); // "\033[0m" Build end char, Resets any ANSI format + * Terminal::build(null, 'u'); // "\033[s" Saves the current cursor position + * Terminal::build(0); // "\033[0m" Build end char, Resets any ANSI format * ``` * * @param mixed $format diff --git a/src/Utils/CliUtil.php b/src/Utils/CliUtil.php index 6cfb1e8..4cb39f8 100644 --- a/src/Utils/CliUtil.php +++ b/src/Utils/CliUtil.php @@ -142,6 +142,62 @@ public static function getTempDir() return $tmp; } + /** + * get screen size + * + * ```php + * list($width, $height) = Helper::getScreenSize(); + * ``` + * @from Yii2 + * @param boolean $refresh whether to force checking and not re-use cached size value. + * This is useful to detect changing window size while the application is running but may + * not get up to date values on every terminal. + * @return array|boolean An array of ($width, $height) or false when it was not able to determine size. + */ + public static function getScreenSize($refresh = false) + { + static $size; + if ($size !== null && !$refresh) { + return $size; + } + + if (self::bashIsAvailable()) { + // try stty if available + $stty = []; + + if ( + exec('stty -a 2>&1', $stty) && + preg_match('/rows\s+(\d+);\s*columns\s+(\d+);/mi', implode(' ', $stty), $matches) + ) { + return ($size = [$matches[2], $matches[1]]); + } + + // fallback to tput, which may not be updated on terminal resize + if (($width = (int)exec('tput cols 2>&1')) > 0 && ($height = (int)exec('tput lines 2>&1')) > 0) { + return ($size = [$width, $height]); + } + + // fallback to ENV variables, which may not be updated on terminal resize + if (($width = (int)getenv('COLUMNS')) > 0 && ($height = (int)getenv('LINES')) > 0) { + return ($size = [$width, $height]); + } + } + + if (Helper::isOnWindows()) { + $output = []; + exec('mode con', $output); + + if (isset($output[1]) && strpos($output[1], 'CON') !== false) { + return ($size = [ + (int)preg_replace('~\D~', '', $output[3]), + (int)preg_replace('~\D~', '', $output[4]) + ]); + } + } + + return ($size = false); + } + /** * @param string $program * @return int|string diff --git a/src/Utils/Helper.php b/src/Utils/Helper.php index 0daafea..8432c34 100644 --- a/src/Utils/Helper.php +++ b/src/Utils/Helper.php @@ -63,6 +63,14 @@ public static function isRoot(): bool return getmyuid() === 0; } + /** + * @return bool + */ + public static function supportColor() + { + return self::isSupportColor(); + } + /** * Returns true if STDOUT supports colorization. * This code has been copied and adapted from @@ -382,62 +390,6 @@ public static function spliceKeyValue(array $data, array $opts = []) return $text; } - /** - * get screen size - * - * ```php - * list($width, $height) = Helper::getScreenSize(); - * ``` - * @from Yii2 - * @param boolean $refresh whether to force checking and not re-use cached size value. - * This is useful to detect changing window size while the application is running but may - * not get up to date values on every terminal. - * @return array|boolean An array of ($width, $height) or false when it was not able to determine size. - */ - public static function getScreenSize($refresh = false) - { - static $size; - if ($size !== null && !$refresh) { - return $size; - } - - if (CliUtil::bashIsAvailable()) { - // try stty if available - $stty = []; - - if ( - exec('stty -a 2>&1', $stty) && - preg_match('/rows\s+(\d+);\s*columns\s+(\d+);/mi', implode(' ', $stty), $matches) - ) { - return ($size = [$matches[2], $matches[1]]); - } - - // fallback to tput, which may not be updated on terminal resize - if (($width = (int)exec('tput cols 2>&1')) > 0 && ($height = (int)exec('tput lines 2>&1')) > 0) { - return ($size = [$width, $height]); - } - - // fallback to ENV variables, which may not be updated on terminal resize - if (($width = (int)getenv('COLUMNS')) > 0 && ($height = (int)getenv('LINES')) > 0) { - return ($size = [$width, $height]); - } - } - - if (self::isOnWindows()) { - $output = []; - exec('mode con', $output); - - if (isset($output[1]) && strpos($output[1], 'CON') !== false) { - return ($size = [ - (int)preg_replace('~\D~', '', $output[3]), - (int)preg_replace('~\D~', '', $output[4]) - ]); - } - } - - return ($size = false); - } - /** * Word wrap text with indentation to fit the screen size * @@ -465,7 +417,7 @@ public static function wrapText($text, $indent = 0, $width = 0) } if ((int)$width <= 0) { - $size = static::getScreenSize(); + $size = CliUtil::getScreenSize(); if ($size === false || $size[0] <= $indent) { return $text; diff --git a/src/Utils/ProcessUtil.php b/src/Utils/ProcessUtil.php index cfb9e1f..cd37131 100644 --- a/src/Utils/ProcessUtil.php +++ b/src/Utils/ProcessUtil.php @@ -14,6 +14,14 @@ */ class ProcessUtil { + /** + * @return bool + */ + public static function isSupported() + { + return !Helper::isWindows() && \function_exists('pcntl_fork'); + } + /** * Daemon, detach and run in the background * @param \Closure|null $beforeQuit @@ -21,6 +29,10 @@ class ProcessUtil */ public static function daemonRun(\Closure $beforeQuit = null) { + if (!self::isSupported()) { + return 0; + } + // umask(0); $pid = pcntl_fork(); @@ -65,42 +77,74 @@ public static function runInBackground($cmd) } /** - * fork multi child processes. + * @see ProcessUtil::forks() * @param int $number - * @param callable|null $childHandler - * @return array|int + * @param callable|null $onStart + * @param callable|null $onError + * @return array|false */ - public static function forks($number, callable $childHandler = null) + public static function multi(int $number, callable $onStart = null, callable $onError = null) { - $num = (int)$number > 0 ? (int)$number : 0; + return self::forks($number, $onStart, $onError); + } - if ($num <= 0) { + /** + * fork/create multi child processes. + * @param int $number + * @param callable|null $onStart Will running on the child processes. + * @param callable|null $onError + * @return array|false + */ + public static function forks(int $number, callable $onStart = null, callable $onError = null) + { + if ($number <= 0) { + return false; + } + + if (!self::isSupported()) { return false; } $pidAry = []; - for ($id = 0; $id < $num; $id++) { - $child = self::fork($id, $childHandler); - $pidAry[$child['pid']] = $child; + for ($id = 0; $id < $number; $id++) { + $info = self::fork($onStart, $onError, $id); + $pidAry[$info['pid']] = $info; } return $pidAry; } /** - * fork a child process. + * @see ProcessUtil::fork() * @param int $id - * @param callable|null $childHandler - * param bool $first - * @return array + * @param callable|null $onStart + * @param callable|null $onError + * @return array|false + */ + public static function create($id = 0, callable $onStart = null, callable $onError = null) + { + return self::fork($id, $onStart, $onError); + } + + /** + * fork/create a child process. + * @param callable|null $onStart Will running on the child process start. + * @param int $id The process index number. will use `forks()` + * @param callable|null $onError + * @return array|false */ - public static function fork($id = 0, callable $childHandler = null) + public static function fork(callable $onStart = null, callable $onError = null, $id = 0) { + if (!self::isSupported()) { + return false; + } + $info = []; $pid = pcntl_fork(); - if ($pid > 0) {// at parent, get forked child info + // at parent, get forked child info + if ($pid > 0) { $info = [ 'id' => $id, 'pid' => $pid, @@ -109,11 +153,14 @@ public static function fork($id = 0, callable $childHandler = null) } elseif ($pid === 0) { // at child $pid = getmypid(); - if ($childHandler) { - $childHandler($id, $pid); + if ($onStart) { + $onStart($id, $pid); } - } else { + if ($onError) { + $onError($pid); + } + Show::error('Fork child process failed! exiting.'); } @@ -127,6 +174,10 @@ public static function fork($id = 0, callable $childHandler = null) */ public static function wait(callable $onExit) { + if (!self::isSupported()) { + return false; + } + $status = null; //pid<0:子进程都没了 @@ -171,6 +222,10 @@ public static function stopChildren(array $children, $signal = SIGTERM, array $e return false; } + if (!self::isSupported()) { + return false; + } + $events = array_merge([ 'beforeStops' => null, 'beforeStop' => null, @@ -273,6 +328,29 @@ public static function killByName($name, $sigNo = 9) return exec($cmd); } + + /** + * @param int $pid + * @return bool + */ + public static function isRunning($pid) + { + return ($pid > 0) && @posix_kill($pid, 0); + } + + /** + * exit + * @param int $code + */ + public static function quit($code = 0) + { + exit((int)$code); + } + + /************************************************************************************** + * process signal handle + *************************************************************************************/ + /** * send signal to the process * @param int $pid @@ -286,6 +364,10 @@ public static function sendSignal($pid, $signal, $timeout = 0) return false; } + if (!self::isSupported()) { + return false; + } + // do send if ($ret = posix_kill($pid, $signal)) { return true; @@ -322,21 +404,24 @@ public static function sendSignal($pid, $signal, $timeout = 0) } /** - * @param int $pid + * install signal + * @param int $signal e.g: SIGTERM SIGINT(Ctrl+C) SIGUSR1 SIGUSR2 SIGHUP + * @param callable $handler * @return bool */ - public static function isRunning($pid) + public static function installSignal($signal, callable $handler) { - return ($pid > 0) && @posix_kill($pid, 0); + return pcntl_signal($signal, $handler, false); } /** - * exit - * @param int $code + * dispatch signal + * @return bool */ - public static function quit($code = 0) + public static function dispatchSignal() { - exit((int)$code); + // receive and dispatch sig + return pcntl_signal_dispatch(); } /************************************************************************************** @@ -404,27 +489,6 @@ public static function afterDo($seconds, callable $handler) pcntl_alarm($seconds); } - /** - * install signal - * @param int $signal e.g: SIGTERM SIGINT(Ctrl+C) SIGUSR1 SIGUSR2 SIGHUP - * @param callable $handler - * @return bool - */ - public static function installSignal($signal, callable $handler) - { - return pcntl_signal($signal, $handler, false); - } - - /** - * dispatch signal - * @return bool - */ - public static function dispatchSignal() - { - // receive and dispatch sig - return pcntl_signal_dispatch(); - } - /** * Set process title. * @param string $title diff --git a/src/Utils/Show.php b/src/Utils/Show.php index 450b445..a6494ea 100644 --- a/src/Utils/Show.php +++ b/src/Utils/Show.php @@ -686,7 +686,7 @@ public static function table(array $data, $title = 'Data Table', array $opts = [ $hasHead = false; $rowIndex = 0; - $head = $table = []; + $head = []; $tableHead = $opts['columns']; $leftIndent = $opts['leftIndent']; $showBorder = $opts['showBorder']; @@ -814,59 +814,94 @@ public static function table(array $data, $title = 'Data Table', array $opts = [ ***********************************************************************************/ /** - * @todo un-completed + * show a spinner icon message + * ```php + * $total = 5000; + * while ($total--) { + * Show::spinner(); + * usleep(100); + * } + * Show::spinner('Done', true); + * ``` + * @param string $msg + * @param bool $ended */ - public static function spinner() + public static function spinner($msg = '', $ended = false) { - static $spinner = 0; - static $lastTime = null; static $chars = '-\|/'; - // static $chars = '-.*.-'; + static $counter = 0; + static $lastTime = null; + + $tpl = (Helper::supportColor() ? "\x0D\x1B[2K" : "\x0D\r") . '%s'; + + if ($ended) { + printf($tpl, $msg); + + return; + } - $tpl = (Helper::isSupportColor() ? "\x0D\x1B[2K" : "\x0D\r") . '%s'; $now = microtime(true); if (null === $lastTime || ($lastTime < $now - 0.1)) { $lastTime = $now; - // echo $chars[$spinner]; - printf($tpl, $chars[$spinner]); - $spinner++; + // echo $chars[$counter]; + printf($tpl, $chars[$counter] . $msg); + $counter++; - if ($spinner > \strlen($chars) - 1) { - $spinner = 0; + if ($counter > \strlen($chars) - 1) { + $counter = 0; } } } /** - * '.' - * '..' - * '...' - * '.' - * @param null $msg + * alias of the pending() + * @param string $msg + * @param bool $ended */ - public static function loading($msg = null) + public static function loading($msg = 'Loading ', $ended = false) { - + self::pending($msg, $ended); } - public static function pending($msg = 'pending') + /** + * show a pending message + * ```php + * $total = 8000; + * + * while ($total--) { + * Show::pending(); + * usleep(200); + * } + * + * Show::pending('Done', true); + * ``` + * @param string $msg + * @param bool $ended + */ + public static function pending($msg = 'Pending ', $ended = false) { - static $spinner = 0; + static $counter = 0; static $lastTime = null; - static $chars = '...'; + static $chars = ['', '.', '..', '...']; + + $tpl = (Helper::supportColor() ? "\x0D\x1B[2K" : "\x0D\r") . '%s'; + + if ($ended) { + printf($tpl, $msg); + + return; + } - $tpl = (Helper::isSupportColor() ? "\x0D\x1B[2K" : "\x0D\r") . '%s'; $now = microtime(true); - if (null === $lastTime || ($lastTime < $now - 0.1)) { + if (null === $lastTime || ($lastTime < $now - 0.8)) { $lastTime = $now; - // echo $chars[$spinner]; - printf($tpl, $chars[$spinner]); - $spinner++; + printf($tpl, $msg . $chars[$counter]); + $counter++; - if ($spinner > \strlen($chars) - 1) { - $spinner = 0; + if ($counter > \count($chars) - 1) { + $counter = 0; } } }