diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 13:23:16 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 13:23:16 +0000 |
commit | 3e97c51418e6d27e9a81906f347fcb7c78e27d4f (patch) | |
tree | ee596ce1bc9840661386f96f9b8d1f919a106317 /vendor/gipfl/cli/src | |
parent | Initial commit. (diff) | |
download | icingaweb2-module-incubator-3e97c51418e6d27e9a81906f347fcb7c78e27d4f.tar.xz icingaweb2-module-incubator-3e97c51418e6d27e9a81906f347fcb7c78e27d4f.zip |
Adding upstream version 0.20.0.upstream/0.20.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gipfl/cli/src')
-rw-r--r-- | vendor/gipfl/cli/src/AnsiScreen.php | 128 | ||||
-rw-r--r-- | vendor/gipfl/cli/src/Process.php | 141 | ||||
-rw-r--r-- | vendor/gipfl/cli/src/Screen.php | 190 | ||||
-rw-r--r-- | vendor/gipfl/cli/src/Spinner.php | 69 | ||||
-rw-r--r-- | vendor/gipfl/cli/src/Tty.php | 132 | ||||
-rw-r--r-- | vendor/gipfl/cli/src/TtyMode.php | 95 |
6 files changed, 755 insertions, 0 deletions
diff --git a/vendor/gipfl/cli/src/AnsiScreen.php b/vendor/gipfl/cli/src/AnsiScreen.php new file mode 100644 index 0000000..2ae3f40 --- /dev/null +++ b/vendor/gipfl/cli/src/AnsiScreen.php @@ -0,0 +1,128 @@ +<?php + +namespace gipfl\Cli; + +use InvalidArgumentException; + +/** + * Screen implementation for screens with ANSI escape code support + * + * @see http://en.wikipedia.org/wiki/ANSI_escape_code + */ +class AnsiScreen extends Screen +{ + const FG_COLORS = [ + 'black' => '30', + 'darkgray' => '1;30', + 'red' => '31', + 'lightred' => '1;31', + 'green' => '32', + 'lightgreen' => '1;32', + 'brown' => '33', + 'yellow' => '1;33', + 'blue' => '34', + 'lightblue' => '1;34', + 'purple' => '35', + 'lightpurple' => '1;35', + 'cyan' => '36', + 'lightcyan' => '1;36', + 'lightgray' => '37', + 'white' => '1;37', + ]; + + const BG_COLORS = [ + 'black' => '40', + 'red' => '41', + 'green' => '42', + 'brown' => '43', + 'blue' => '44', + 'purple' => '45', + 'cyan' => '46', + 'lightgray' => '47', + ]; + + /** + * Remove all ANSI escape codes from a given string + * @param $string + * @return string|string[]|null + */ + public function stripAnsiCodes($string) + { + return \preg_replace('/\e\[?.*?[@-~]/', '', $string); + } + + public function clear() + { + return "\033[2J" // Clear the whole screen + . "\033[1;1H" // Move the cursor to row 1, column 1 + . "\033[1S"; // Scroll whole page up by 1 line (why?) + } + + public function colorize($text, $fgColor = null, $bgColor = null) + { + return $this->startColor($fgColor, $bgColor) + . $text + . "\033[0m"; // Reset color codes + } + + public function strlen($string) + { + return parent::strlen($this->stripAnsiCodes($string)); + } + + public function underline($text) + { + return "\033[4m" + . $text + . "\033[0m"; // Reset color codes + } + + protected function fgColor($color) + { + if (! \array_key_exists($color, static::FG_COLORS)) { + throw new InvalidArgumentException( + "There is no such foreground color: $color" + ); + } + + return static::FG_COLORS[$color]; + } + + protected function bgColor($color) + { + if (! \array_key_exists($color, static::BG_COLORS)) { + throw new InvalidArgumentException( + "There is no such background color: $color" + ); + } + + return static::BG_COLORS[$color]; + } + + protected function startColor($fgColor = null, $bgColor = null) + { + $parts = []; + if ($fgColor !== null + && $bgColor !== null + && ! \array_key_exists($bgColor, static::BG_COLORS) + && \array_key_exists($bgColor, static::FG_COLORS) + && \array_key_exists($fgColor, static::BG_COLORS) + ) { + $parts[] = '7'; // reverse video, negative image + $parts[] = $this->bgColor($fgColor); + $parts[] = $this->fgColor($bgColor); + } else { + if ($fgColor !== null) { + $parts[] = $this->fgColor($fgColor); + } + if ($bgColor !== null) { + $parts[] = $this->bgColor($bgColor); + } + } + if (empty($parts)) { + return ''; + } + + return "\033[" . \implode(';', $parts) . 'm'; + } +} diff --git a/vendor/gipfl/cli/src/Process.php b/vendor/gipfl/cli/src/Process.php new file mode 100644 index 0000000..45c67b5 --- /dev/null +++ b/vendor/gipfl/cli/src/Process.php @@ -0,0 +1,141 @@ +<?php + +namespace gipfl\Cli; + +class Process +{ + /** @var string|null */ + protected static $initialCwd; + + /** + * Set the command/process title for this process + * + * @param $title + */ + public static function setTitle($title) + { + if (function_exists('cli_set_process_title')) { + \cli_set_process_title($title); + } + } + + /** + * Replace this process with a new instance of itself by executing the + * very same binary with the very same parameters + */ + public static function restart() + { + // _ is only available when executed via shell + $binary = static::getEnv('_'); + $argv = $_SERVER['argv']; + if (\strlen($binary) === 0) { + // Problem: this doesn't work if we changed working directory and + // called the binary with a relative path. Something that doesn't + // happen when started as a daemon, and when started manually we + // should have $_ from our shell. + $binary = static::absoluteFilename(\array_shift($argv)); + } else { + \array_shift($argv); + } + \pcntl_exec($binary, $argv, static::getEnv()); + } + + /** + * Get the given ENV variable, null if not available + * + * Returns an array with all ENV variables if no $key is given + * + * @param string|null $key + * @return array|string|null + */ + public static function getEnv($key = null) + { + if ($key !== null) { + return \getenv($key); + } + + if (PHP_VERSION_ID > 70100) { + return \getenv(); + } else { + $env = $_SERVER; + unset($env['argv'], $env['argc']); + + return $env; + } + } + + /** + * Get the path to the executed binary when starting this command + * + * This fails if we changed working directory and called the binary with a + * relative path. Something that doesn't happen when started as a daemon. + * When started manually we should have $_ from our shell. + * + * To be always on the safe side please call Process::getInitialCwd() once + * after starting your process and before switching directory. That way we + * preserve our initial working directory. + * + * @return mixed|string + */ + public static function getBinaryPath() + { + if (isset($_SERVER['_'])) { + return $_SERVER['_']; + } else { + global $argv; + + return static::absoluteFilename($argv[0]); + } + } + + /** + * The working directory as given by getcwd() the very first time we + * called this method + * + * @return string + */ + public static function getInitialCwd() + { + if (self::$initialCwd === null) { + self::$initialCwd = \getcwd(); + } + + return self::$initialCwd; + } + + /** + * Returns the absolute filename for the given file + * + * If relative, it's calculated in relation to the given working directory. + * The current working directory is being used if null is given. + * + * @param $filename + * @param null $cwd + * @return string + */ + public static function absoluteFilename($filename, $cwd = null) + { + $filename = \str_replace( + DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR, + DIRECTORY_SEPARATOR, + $filename + ); + if ($filename[0] === '.') { + $filename = ($cwd ?: \getcwd()) . DIRECTORY_SEPARATOR . $filename; + } + $parts = \explode(DIRECTORY_SEPARATOR, $filename); + $result = []; + foreach ($parts as $part) { + if ($part === '.') { + continue; + } + if ($part === '..') { + \array_pop($result); + continue; + } + $result[] = $part; + } + + return \implode(DIRECTORY_SEPARATOR, $result); + } +} diff --git a/vendor/gipfl/cli/src/Screen.php b/vendor/gipfl/cli/src/Screen.php new file mode 100644 index 0000000..cb05a3f --- /dev/null +++ b/vendor/gipfl/cli/src/Screen.php @@ -0,0 +1,190 @@ +<?php + +namespace gipfl\Cli; + +/** + * Base class providing minimal CLI Screen functionality. While classes + * extending this one (read: AnsiScreen) should implement all the fancy cool + * things, this base class makes sure that your code will still run in + * environments with no ANSI or similar support + * + * ```php + * $screen = Screen::instance(); + * echo $screen->center($screen->underline('Hello world')); + * ``` + */ +class Screen +{ + protected $isUtf8; + + /** + * Get a new Screen instance. + * + * For now this is limited to either a very basic Screen implementation as + * a fall-back or an AnsiScreen implementation with more functionality + * + * @return AnsiScreen|Screen + */ + public static function factory() + { + if (! defined('STDOUT')) { + return new Screen(); + } + if (\function_exists('posix_isatty') && \posix_isatty(STDOUT)) { + return new AnsiScreen(); + } else { + return new Screen(); + } + } + + /** + * Center the given string horizontally on the current screen + * + * @param $string + * @return string + */ + public function center($string) + { + $len = $this->strlen($string); + $width = (int) \floor(($this->getColumns() + $len) / 2) - $len; + + return \str_repeat(' ', $width) . $string; + } + + /** + * Clear the screen + * + * Impossible for non-ANSI screens, so let's output a newline for now + * + * @return string + */ + public function clear() + { + return "\n"; + } + + /** + * Colorize the given text. Has no effect on a basic Screen, all colors + * will be accepted. It's prefectly legal to provide background or foreground + * only + * + * Returns the very same string, eventually enriched with related ANSI codes + * + * @param $text + * @param null $fgColor + * @param null $bgColor + * + * @return mixed + */ + public function colorize($text, $fgColor = null, $bgColor = null) + { + return $text; + } + + /** + * Generate $count newline characters + * + * @param int $count + * @return string + */ + public function newlines($count = 1) + { + return \str_repeat(PHP_EOL, $count); + } + + /** + * Calculate the visible length of a given string. While this is simple on + * a non-ANSI-screen, such implementation will be required to strip control + * characters to get the correct result + * + * @param $string + * @return int + */ + public function strlen($string) + { + if ($this->isUtf8()) { + return \mb_strlen($string, 'UTF-8'); + } else { + return \strlen($string); + } + } + + /** + * Underline the given text - if possible + * + * @return string + */ + public function underline($text) + { + return $text; + } + + /** + * Get the number of currently available columns. Please note that this + * might chance at any time while your program is running + * + * @return int + */ + public function getColumns() + { + $cols = (int) \getenv('COLUMNS'); + if (! $cols) { + // stty -a ? + $cols = (int) \exec('tput cols'); + } + if (! $cols) { + $cols = 80; + } + + return $cols; + } + + /** + * Get the number of currently available rows. Please note that this + * might chance at any time while your program is running + * + * @return int + */ + public function getRows() + { + $rows = (int) \getenv('ROWS'); + if (! $rows) { + // stty -a ? + $rows = (int) \exec('tput lines'); + } + if (! $rows) { + $rows = 25; + } + + return $rows; + } + + /** + * Whether we're on a UTF-8 screen. We assume latin1 otherwise, there is no + * support for additional encodings + * + * @return bool + */ + public function isUtf8() + { + if ($this->isUtf8 === null) { + // null should equal 0 here, however seems to equal '' on some systems: + $current = \setlocale(LC_ALL, 0); + + $parts = explode(';', $current); + $lc_parts = []; + foreach ($parts as $part) { + if (\strpos($part, '=') === false) { + continue; + } + list($key, $val) = explode('=', $part, 2); + $lc_parts[$key] = $val; + } + + $this->isUtf8 = \array_key_exists('LC_CTYPE', $lc_parts) + && \preg_match('~\.UTF-8$~i', $lc_parts['LC_CTYPE']); + } + + return $this->isUtf8; + } +} diff --git a/vendor/gipfl/cli/src/Spinner.php b/vendor/gipfl/cli/src/Spinner.php new file mode 100644 index 0000000..b949526 --- /dev/null +++ b/vendor/gipfl/cli/src/Spinner.php @@ -0,0 +1,69 @@ +<?php + +namespace gipfl\Cli; + +use React\EventLoop\LoopInterface; +use React\Promise\Deferred; +use React\Promise\ExtendedPromiseInterface; + +class Spinner +{ + const ASCII_SLASH = ['/', '-', '\\', '|']; + const ASCII_BOUNCING_CIRCLE = ['.', 'o', 'O', '°', 'O', 'o']; + const ROTATING_HALF_CIRCLE = ['◑', '◒', '◐', '◓']; + const ROTATING_EARTH = ['🌎', '🌏', '🌍']; + const ROTATING_MOON = ['🌑', '🌒', '🌓', '🌔', '🌕', '🌖', '🌗', '🌘']; + const UP_DOWN_BAR = [' ', '_', '▁', '▃', '▄', '▅', '▆', '▇', '▆', '▅', '▄', '▃', '▁']; + const CLOCK = ['🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗', '🕘', '🕙', '🕚', '🕛']; + const WAVING_DOTS = ['⢄', '⢂', '⢁', '⡁', '⡈', '⡐', '⡠', '⡐', '⡈', '⡁', '⢁', '⢂']; + const ROTATING_DOTS = ['⣷', '⣯', '⣟', '⡿', '⢿', '⣻', '⣽', '⣾']; + + /** @var LoopInterface */ + protected $loop; + + protected $frames; + + protected $frame = -1; + + protected $count; + + protected $delay; + + public function __construct(LoopInterface $loop, array $frames = self::ASCII_SLASH) + { + $this->loop = $loop; + $this->frames = $frames; + $this->count = \count($frames); + $this->delay = ((int) (2 * 100 / $this->count)) / 100; + } + + protected function getNextFrame() + { + $first = $this->frame === -1; + $this->frame++; + if ($this->frame >= $this->count) { + $this->frame = 0; + } + + return $this->frames[$this->frame]; + } + + public function spinWhile(ExtendedPromiseInterface $promise, callable $renderer) + { + $next = function () use ($renderer) { + $renderer($this->getNextFrame()); + }; + $spinTimer = $this->loop->addPeriodicTimer($this->delay, $next); + $deferred = new Deferred(function () use ($spinTimer) { + $this->loop->cancelTimer($spinTimer); + }); + $this->loop->futureTick($next); + $wait = $deferred->promise(); + $cancel = function () use ($wait) { + $wait->cancel(); + }; + $promise->otherwise($cancel)->then($cancel); + + return $promise; + } +} diff --git a/vendor/gipfl/cli/src/Tty.php b/vendor/gipfl/cli/src/Tty.php new file mode 100644 index 0000000..efe5924 --- /dev/null +++ b/vendor/gipfl/cli/src/Tty.php @@ -0,0 +1,132 @@ +<?php + +namespace gipfl\Cli; + +use InvalidArgumentException; +use React\EventLoop\LoopInterface; +use React\Stream\ReadableResourceStream; +use React\Stream\WritableResourceStream; +use RuntimeException; +use function defined; +use function fstat; +use function function_exists; +use function is_bool; +use function is_resource; +use function is_string; +use function posix_isatty; +use function register_shutdown_function; +use function stream_isatty; +use function stream_set_blocking; +use function strlen; +use function var_export; + +class Tty +{ + protected $stdin; + + protected $stdout; + + protected $loop; + + protected $echo = true; + + /** @var TtyMode */ + protected $ttyMode; + + public function __construct(LoopInterface $loop) + { + $this->loop = $loop; + register_shutdown_function([$this, 'restore']); + $loop->futureTick(function () { + $this->initialize(); + }); + } + + public function setEcho($echo) + { + if (! is_bool($echo) && ! is_string($echo) && strlen($echo) !== 1) { + throw new InvalidArgumentException( + "\$echo must be boolean or a single character, got " . var_export($echo, 1) + ); + } + $this->echo = $echo; + if ($this->ttyMode) { + if ($echo) { + $this->ttyMode->enableFeature('echo'); + } else { + $this->ttyMode->disableFeature('echo'); + } + } + + return $this; + } + + public function stdin() + { + if ($this->stdin === null) { + $this->assertValidStdin(); + $this->stdin = new ReadableResourceStream(STDIN, $this->loop); + } + + return $this->stdin; + } + + protected function hasStdin() + { + return defined('STDIN') && is_resource(STDIN) && fstat(STDIN) !== false; + } + + protected function assertValidStdin() + { + if (! $this->hasStdin()) { + throw new RuntimeException('I have no STDIN'); + } + } + + public function stdout() + { + if ($this->stdout === null) { + $this->assertValidStdout(); + $this->stdout = new WritableResourceStream(STDOUT, $this->loop); + } + + return $this->stdout; + } + + protected function hasStdout() + { + return defined('STDOUT') && is_resource(STDOUT) && fstat(STDOUT) !== false; + } + + protected function assertValidStdout() + { + if (! $this->hasStdout()) { + throw new RuntimeException('I have no STDOUT'); + } + } + + protected function initialize() + { + $this->ttyMode = new TtyMode(); + $this->ttyMode->setPreferredMode($this->echo); + } + + public static function isSupported() + { + if (PHP_VERSION_ID >= 70200) { + return stream_isatty(STDIN); + } elseif (function_exists('posix_isatty')) { + return posix_isatty(STDIN); + } else { + return false; + } + } + + public function restore() + { + if ($this->hasStdin()) { + // ReadableResourceStream sets blocking to false, let's restore this + stream_set_blocking(STDIN, true); + } + } +} diff --git a/vendor/gipfl/cli/src/TtyMode.php b/vendor/gipfl/cli/src/TtyMode.php new file mode 100644 index 0000000..8b9c884 --- /dev/null +++ b/vendor/gipfl/cli/src/TtyMode.php @@ -0,0 +1,95 @@ +<?php + +namespace gipfl\Cli; + +use function escapeshellarg; +use function register_shutdown_function; +use function rtrim; +use function shell_exec; + +class TtyMode +{ + protected $originalMode; + + public function enableCanonicalMode() + { + $this->enableFeature('icanon'); + + return $this; + } + + public function disableCanonicalMode() + { + $this->disableFeature('icanon'); + + return $this; + } + + public function enableFeature(...$feature) + { + $this->preserve(); + $cmd = 'stty '; + foreach ($feature as $f) { + $cmd .= escapeshellarg($f); + } + + shell_exec($cmd); + } + + public function disableFeature(...$feature) + { + $this->preserve(); + $cmd = 'stty'; + foreach ($feature as $f) { + $cmd .= ' -' . escapeshellarg($f); + } + + shell_exec($cmd); + } + + public function getCurrentMode() + { + return rtrim(shell_exec('stty -g'), PHP_EOL); + } + + /** + * Helper allowing to call stty only once for the mose used flags, icanon and echo + * @param bool $echo + * @return $this + */ + public function setPreferredMode($echo = true) + { + $this->preserve(); + if ($echo) { + $this->disableFeature('icanon'); + } else { + $this->disableFeature('icanon', 'echo'); + } + + return $this; + } + + /** + * @internal + */ + public function preserve($force = false) + { + if ($force || $this->originalMode === null) { + $this->originalMode = $this->getCurrentMode(); + register_shutdown_function([$this, 'restore']); + } + + return $this; + } + + /** + * @internal + */ + public function restore() + { + if ($this->originalMode) { + shell_exec('stty ' . escapeshellarg($this->originalMode)); + $this->originalMode = null; + } + } +} |