├── LICENSE ├── composer.json └── src └── Snake ├── Contracts ├── Color.php └── SpinnerInterface.php ├── Core └── Driver.php └── Spinner.php /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2019-2020 Alec Rabbit 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alecrabbit/php-cli-snake", 3 | "type": "library", 4 | "description": "Lightweight cli spinner with zero dependencies", 5 | "keywords": [ 6 | "php", 7 | "spinner", 8 | "console", 9 | "cli", 10 | "color", 11 | "256color", 12 | "8bit", 13 | "colorful", 14 | "ansi", 15 | "snake" 16 | ], 17 | "license": "MIT", 18 | "require": { 19 | "php": "^7.3 || ^7.4 || ^8.0" 20 | }, 21 | "require-dev": { 22 | "nunomaduro/collision": "^4.2", 23 | "phpunit/phpunit": "^9.5", 24 | "react/event-loop": "^1.1", 25 | "symfony/var-dumper": "^5.1" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "AlecRabbit\\Snake\\": "src\\Snake" 30 | } 31 | }, 32 | "autoload-dev": { 33 | "psr-4": { 34 | "AlecRabbit\\Tests\\Snake\\": "tests" 35 | } 36 | }, 37 | "minimum-stability": "beta", 38 | "prefer-stable": true 39 | } 40 | -------------------------------------------------------------------------------- /src/Snake/Contracts/Color.php: -------------------------------------------------------------------------------- 1 | setColorLevel($colorLevel); 23 | } 24 | 25 | /** 26 | * @param int $colorLevel 27 | */ 28 | public function setColorLevel(int $colorLevel): void 29 | { 30 | if (!in_array($colorLevel, Color::ALLOWED, true)) { 31 | throw new \InvalidArgumentException('Unknown color level.'); 32 | } 33 | $this->colorLevel = $colorLevel; 34 | } 35 | 36 | public function moveBackSequence(): string 37 | { 38 | return "\033[1D"; 39 | } 40 | 41 | public function eraseSequence(): string 42 | { 43 | return "\033[1X"; 44 | } 45 | 46 | public function frameSequence(int $fg, string $char): string 47 | { 48 | if (Color::COLOR_256 === $this->colorLevel) { 49 | return "\033[38;5;{$fg}m{$char}\033[0m"; 50 | } 51 | if (Color::COLOR_16 === $this->colorLevel) { 52 | return "\033[96m{$char}\033[0m"; 53 | } 54 | return $char; 55 | } 56 | 57 | public function hideCursor(): void 58 | { 59 | $this->write(self::HIDE_CURSOR_SEQ); 60 | } 61 | 62 | /** 63 | * @codeCoverageIgnore 64 | * 65 | * @param string ...$text 66 | */ 67 | public function write(string ...$text): void 68 | { 69 | foreach ($text as $s) { 70 | if (false === $this->stream) { 71 | echo $s; 72 | } elseif (false === @fwrite($this->stream, $s)) { 73 | // should never happen 74 | throw new \RuntimeException('Unable to write stream.'); 75 | } 76 | } 77 | if (false !== $this->stream) { 78 | fflush($this->stream); 79 | } 80 | } 81 | 82 | public function showCursor(): void 83 | { 84 | $this->write(self::SHOW_CURSOR_SEQ); 85 | } 86 | 87 | /** 88 | * @codeCoverageIgnore 89 | */ 90 | public function useStdOut(): void 91 | { 92 | $this->stream = STDOUT; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Snake/Spinner.php: -------------------------------------------------------------------------------- 1 | driver = new Driver($colorLevel); 91 | $this->framesCount = count(self::CHARS); 92 | $this->colorCount = count(self::COLORS); 93 | } 94 | 95 | public function spin(): void 96 | { 97 | $this->driver->write( 98 | $this->driver->eraseSequence(), 99 | $this->driver->frameSequence( 100 | self::COLORS[$this->currentColorIdx], 101 | self::CHARS[$this->currentCharIdx] 102 | ), 103 | $this->driver->moveBackSequence() 104 | ); 105 | $this->update(); 106 | } 107 | 108 | private function update(): void 109 | { 110 | if (++$this->currentCharIdx === $this->framesCount) { 111 | $this->currentCharIdx = 0; 112 | } 113 | if (++$this->currentColorIdx === $this->colorCount) { 114 | $this->currentColorIdx = 0; 115 | } 116 | } 117 | 118 | /** @inheritDoc */ 119 | public function interval(): float 120 | { 121 | return 0.1; 122 | } 123 | 124 | public function begin(): void 125 | { 126 | $this->driver->hideCursor(); 127 | } 128 | 129 | public function end(): void 130 | { 131 | $this->erase(); 132 | $this->driver->showCursor(); 133 | } 134 | 135 | public function erase(): void 136 | { 137 | $this->driver->write( 138 | $this->driver->eraseSequence() 139 | ); 140 | } 141 | 142 | public function useStdOut(): void 143 | { 144 | $this->driver->useStdOut(); 145 | } 146 | } 147 | --------------------------------------------------------------------------------