├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── example.php ├── phpunit.xml.dist ├── src └── ConsoleKit │ ├── Colors.php │ ├── Command.php │ ├── Console.php │ ├── ConsoleException.php │ ├── DefaultOptionsParser.php │ ├── EchoTextWriter.php │ ├── FileSystem.php │ ├── FormatedWriter.php │ ├── Help.php │ ├── HelpCommand.php │ ├── OptionsParser.php │ ├── StdTextWriter.php │ ├── TextFormater.php │ ├── TextWriter.php │ ├── Utils.php │ └── Widgets │ ├── AbstractWidget.php │ ├── Box.php │ ├── Checklist.php │ ├── Dialog.php │ └── ProgressBar.php └── tests ├── ConsoleKit └── Tests │ ├── ColorsTest.php │ ├── ConsoleKitTestCase.php │ ├── ConsoleTest.php │ ├── DefautOptionsParserTest.php │ ├── TestCommand.php │ ├── TestStatic.php │ ├── TestSubCommand.php │ ├── TextFormaterTest.php │ └── UtilsTest.php └── bootstrap.php /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 5.3 4 | - 5.4 5 | script: phpunit 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 Maxime Bouroumeau-Fuseau 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 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, 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, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ConsoleKit 2 | 3 | PHP 5.3+ library to create command line utilities. 4 | 5 | [![Build Status](https://travis-ci.org/maximebf/ConsoleKit.png?branch=master)](http://travis-ci.org/maximebf/ConsoleKit) 6 | 7 | ## Example 8 | 9 | In *cli.php*: 10 | 11 | writeln('hello world!', ConsoleKit\Colors::GREEN); 18 | } 19 | } 20 | 21 | $console = new ConsoleKit\Console(); 22 | $console->addCommand('HelloCommand'); 23 | $console->run(); 24 | 25 | In the shell: 26 | 27 | $ php cli.php hello 28 | hello world! 29 | 30 | More examples in [example.php](https://github.com/maximebf/ConsoleKit/blob/master/example.php) 31 | 32 | ## Installation 33 | 34 | The easiest way to install ConsoleKit is using [Composer](https://github.com/composer/composer) 35 | with the following requirement: 36 | 37 | { 38 | "require": { 39 | "maximebf/consolekit": ">=1.0.0" 40 | } 41 | } 42 | 43 | Alternatively, you can [download the archive](https://github.com/maximebf/ConsoleKit/zipball/master) 44 | and add the src/ folder to PHP's include path: 45 | 46 | set_include_path('/path/to/src' . PATH_SEPARATOR . get_include_path()); 47 | 48 | ConsoleKit does not provide an autoloader but follows the [PSR-0 convention](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md). 49 | You can use the following snippet to autoload ConsoleKit classes: 50 | 51 | spl_autoload_register(function($className) { 52 | if (substr($className, 0, 10) === 'ConsoleKit') { 53 | $filename = str_replace('\\', DIRECTORY_SEPARATOR, trim($className, '\\')) . '.php'; 54 | require_once $filename; 55 | } 56 | }); 57 | 58 | ## Usage 59 | 60 | ### Options parser 61 | 62 | The default options parser parses an argv-like array. 63 | Items can be of the form: 64 | 65 | - --key=value 66 | - --key 67 | - -a 68 | - -ab (equivalent of -a -b) 69 | 70 | When an option has no value, true will be used. If multiple key/value pairs 71 | with the same key are specified, the "key" value will be an array containing all the values. 72 | If "--" is detected, all folowing values will be treated as a single argument 73 | 74 | Example: the string "-a -bc --longopt --key=value arg1 arg2 -- --any text" will produce the following two arrays: 75 | 76 | $args = array('arg1', 'arg2', '--any text'); 77 | $options = array('a' => true, 'b' => true, 'c' => true, 'longopt' => true, 'key' => 'value'); 78 | 79 | ### Creating commands 80 | 81 | Any callbacks can be a command. It will receive three parameters: the 82 | arguments array, the options array and the console object. 83 | 84 | function my_command($args, $opts, $console) { 85 | $console->writeln("hello world!"); 86 | } 87 | 88 | Commands can also be defined as classes. In this case, they must inherit from `ConsoleKit\Command` 89 | and override the `execute()` method. 90 | 91 | class MyCommand extends ConsoleKit\Command { 92 | public function execute(array $args, array $opts) { 93 | $this->writeln("hello world!"); 94 | } 95 | } 96 | 97 | The `ConsoleKit\Command` class offers helper methods, check it out for more info. 98 | 99 | ### Registering commands 100 | 101 | Commands need to be registered in the console object using the `addCommand()` method (or `addCommands()`). 102 | 103 | $console = new ConsoleKit\Console(); 104 | $console->addCommand('my_command'); // the my_command function 105 | $console->addCommand('MyCommand'); // the MyCommand class 106 | $console->addCommand(function() { echo 'hello!'; }, 'hello'); // using a closure 107 | // or: 108 | $console->addCommand('hello', function() { echo 'hello!'; }); // alternative when using a closure 109 | 110 | Notice that in the last example we have provided a second argument which is an alias for a command. 111 | As closures have no name, one must be specified. 112 | 113 | The command name for functions is the same as the function name with underscores replaced 114 | by dashes (ie. my\_command becomes my-command). 115 | 116 | The command name for command classes is the short class name without the `Command` 117 | suffix and "dashized" (ie. HelloWorldCommand becomes hello-world). 118 | 119 | ### Running 120 | 121 | Simply call the `run()` method of the console object 122 | 123 | $console->run(); 124 | $console->run(array('custom arg1', 'custom arg2')); // overrides $_SERVER['argv'] 125 | 126 | ### Automatic help generation 127 | 128 | The *help* command is automatically registered and provides help about available methods based on doc comments. 129 | Check out [example.php](https://github.com/maximebf/ConsoleKit/blob/master/example.php) for example of available tags 130 | 131 | $ php myscript.php help 132 | 133 | ## Formating text 134 | 135 | ### Colors 136 | 137 | The `ConsoleKit\Colors::colorize()` method provides an easy way to colorize a text. 138 | Colors are defined as either a string or an integer (through constants of the `Colors` class). 139 | Available colors: black, red, green, yellow, blue, magenta, cyan, white. 140 | 141 | Foreground colors are also available in a "bold" variant. Suffix the color name with "+bold" or use the OR bit operator with constants. 142 | 143 | echo Colors::colorize('my red text', Colors::RED); 144 | echo Colors::colorize('my red text', 'red'); 145 | 146 | echo Colors::colorize('my red bold text', Colors::RED | Colors::BOLD); 147 | echo Colors::colorize('my red bold text', 'red+bold'); 148 | 149 | echo Colors::colorize('my red text over yellow background', Colors::RED, Colors::YELLOW); 150 | 151 | ### TextFormater 152 | 153 | The `ConsoleKit\TextFormater` class allows you to format text using the following options: 154 | 155 | - indentation using `setIndent()` or the *indent* option 156 | - quoting using `setQuote()` or the *quote* option 157 | - foreground color using `setFgColor()` or the *fgcolor* option 158 | - background color using `setBgColor()` or the *bgcolor* option 159 | 160 | Options can be defined using `setOptions()` or as the first parameter of the constructor. 161 | 162 | $formater = new ConsoleKit\TextFormater(array('quote' => ' > ')); 163 | echo $formater->format("hello!"); 164 | // produces: " > hello" 165 | 166 | ## Widgets 167 | 168 | ### Dialog 169 | 170 | Used to interact with the user 171 | 172 | $dialog = new ConsoleKit\Widgets\Dialog($console); 173 | $name = $dialog->ask('What is your name?'); 174 | if ($dialog->confirm('Are you sure?')) { 175 | $console->writeln("hello $name"); 176 | } 177 | 178 | ### Box 179 | 180 | Wraps text in a box 181 | 182 | $box = new ConsoleKit\Widgets\Box($console, 'my text'); 183 | $box->write(); 184 | 185 | Produces: 186 | 187 | ******************************************** 188 | * my text * 189 | ******************************************** 190 | 191 | ### Progress bar 192 | 193 | Displays a progress bar 194 | 195 | $total = 100; 196 | $progress = new ConsoleKit\Widgets\ProgressBar($console, $total); 197 | for ($i = 0; $i < $total; $i++) { 198 | $progress->incr(); 199 | usleep(10000); 200 | } 201 | $progress->stop(); 202 | 203 | 204 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "maximebf/consolekit", 3 | "description": "Library to create command line utilities", 4 | "keywords": ["console", "shell", "cli", "commands"], 5 | "homepage": "https://github.com/maximebf/ConsoleKit", 6 | "type": "library", 7 | "license": "MIT", 8 | "authors": [{ 9 | "name": "Maxime Bouroumeau-Fuseau", 10 | "email": "maxime.bouroumeau@gmail.com", 11 | "homepage": "http://maximebf.com" 12 | }], 13 | "require": { 14 | "php": ">=5.3.0" 15 | }, 16 | "autoload": { 17 | "psr-0": {"ConsoleKit": "src/"} 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /example.php: -------------------------------------------------------------------------------- 1 | writeln('hello world!', Colors::GREEN); 20 | } 21 | } 22 | 23 | /** 24 | * Says hello to someone 25 | * 26 | * @arg name The name of the person to say hello to 27 | * @opt color The color in which to print the text 28 | */ 29 | class SayHelloCommand extends Command 30 | { 31 | public function execute(array $args, array $options = array()) 32 | { 33 | $this->context(array('fgcolor' => Utils::get($options, 'color')), function($c) use ($args) { 34 | $c->writeln(sprintf('hello %s!', $args[0])); 35 | }); 36 | } 37 | } 38 | 39 | /** 40 | * Commands to say something to someone! 41 | */ 42 | class SayCommand extends Command 43 | { 44 | /** 45 | * Says hello to someone 46 | * 47 | * @arg name The name of the person to say hello to 48 | */ 49 | public function executeHello(array $args, array $options = array()) 50 | { 51 | $name = 'unknown'; 52 | if (empty($args)) { 53 | $dialog = new Dialog($this->console); 54 | $name = $dialog->ask('What is your name?', $name); 55 | } else { 56 | $name = $args[0]; 57 | } 58 | $this->writeln(sprintf('hello %s!', $name)); 59 | } 60 | 61 | /** 62 | * Says hi to someone 63 | * 64 | * @arg name The name of the person to say hello to 65 | */ 66 | public function executeHi(array $args, array $options = array()) 67 | { 68 | $this->writeln(sprintf('hi %s!', $args[0])); 69 | } 70 | } 71 | 72 | /** 73 | * Displays a progress bar 74 | * 75 | * @opt total Number of iterations 76 | * @opt usleep Waiting time in microsecond between each iteration 77 | */ 78 | function progress($args, $options, $console) 79 | { 80 | $total = isset($options['total']) ? $options['total'] : 100; 81 | $usleep = isset($options['usleep']) ? $options['usleep'] : 10000; 82 | $progress = new ProgressBar($console, $total); 83 | for ($i = 0; $i < $total; $i++) { 84 | $progress->incr(); 85 | usleep($usleep); 86 | } 87 | $progress->stop(); 88 | } 89 | 90 | $console = new Console(array( 91 | 'hello' => 'HelloWorldCommand', 92 | 'SayHelloCommand', 93 | 'SayCommand', 94 | 'progress' 95 | )); 96 | 97 | $console->run(); 98 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | ./tests/ConsoleKit/ 17 | 18 | 19 | 20 | 21 | 22 | ./src/ConsoleKit/ 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/ConsoleKit/Colors.php: -------------------------------------------------------------------------------- 1 | 19 | * $text = Colors::colorize('hello world', Colors::RED) 20 | * $text = Colors::colorize('hello world', 'red') 21 | * $text = Colors::colorize('hello world', Colors::RED | Colors::BOLD) 22 | * $text = Colors::colorize('hello world', 'red+bold') 23 | * $text = Colors::red('hello world'); 24 | * 25 | */ 26 | class Colors 27 | { 28 | const RESET = "\033[0m"; 29 | 30 | const BLACK = 1; 31 | const RED = 2; 32 | const GREEN = 4; 33 | const YELLOW = 8; 34 | const BLUE = 16; 35 | const MAGENTA = 32; 36 | const CYAN = 64; 37 | const WHITE = 128; 38 | 39 | const BOLD = 256; 40 | const UNDERSCORE = 512; 41 | const BLINK = 1024; 42 | const REVERSE = 2048; 43 | const CONCEAL = 4096; 44 | 45 | /** @var array */ 46 | private static $colors = array( 47 | 'black' => self::BLACK, 48 | 'red' => self::RED, 49 | 'green' => self::GREEN, 50 | 'yellow' => self::YELLOW, 51 | 'blue' => self::BLUE, 52 | 'magenta' => self::MAGENTA, 53 | 'cyan' => self::CYAN, 54 | 'white' => self::WHITE 55 | ); 56 | 57 | /** @var array */ 58 | private static $options = array( 59 | 'bold' => self::BOLD, 60 | 'underscore' => self::UNDERSCORE, 61 | 'blink' => self::BLINK, 62 | 'reverse' => self::REVERSE, 63 | 'conceal' => self::CONCEAL 64 | ); 65 | 66 | /** @var array */ 67 | private static $codes = array( 68 | self::BLACK => 0, 69 | self::RED => 1, 70 | self::GREEN => 2, 71 | self::YELLOW => 3, 72 | self::BLUE => 4, 73 | self::MAGENTA => 5, 74 | self::CYAN => 6, 75 | self::WHITE => 7, 76 | self::BOLD => 1, 77 | self::UNDERSCORE => 4, 78 | self::BLINK => 5, 79 | self::REVERSE => 7, 80 | self::CONCEAL => 8 81 | ); 82 | 83 | /** 84 | * Returns a colorized string 85 | * 86 | * @param string $text 87 | * @param string $fgcolor (a key from the $foregroundColors array) 88 | * @param string $bgcolor (a key from the $backgroundColors array) 89 | * @return string 90 | */ 91 | public static function colorize($text, $fgcolor = null, $bgcolor = null) 92 | { 93 | $colors = ''; 94 | if ($bgcolor) { 95 | $colors .= self::getBgColorString(self::getColorCode($bgcolor)); 96 | } 97 | if ($fgcolor) { 98 | $colors .= self::getFgColorString(self::getColorCode($fgcolor)); 99 | } 100 | if ($colors) { 101 | $text = $colors . $text . self::RESET; 102 | } 103 | return $text; 104 | } 105 | 106 | /** 107 | * Returns a text with each lines colorized independently 108 | * 109 | * @param string $text 110 | * @param string $fgcolor 111 | * @param string $bgcolor 112 | * @return string 113 | */ 114 | public static function colorizeLines($text, $fgcolor = null, $bgcolor = null) 115 | { 116 | $lines = explode("\n", $text); 117 | foreach ($lines as &$line) { 118 | $line = self::colorize($line, $fgcolor, $bgcolor); 119 | } 120 | return implode("\n", $lines); 121 | } 122 | 123 | /** 124 | * Returns a color code 125 | * 126 | * $color can be a string with the color name, or one of the color constants. 127 | * 128 | * @param int|string $color 129 | * @param array $options 130 | * @return int 131 | */ 132 | public static function getColorCode($color, $options = array()) 133 | { 134 | $code = (int) $color; 135 | if (is_string($color)) { 136 | $options = array_merge(explode('+', strtolower($color)), $options); 137 | $color = array_shift($options); 138 | if (!isset(self::$colors[$color])) { 139 | throw new ConsoleException("Unknown color '$color'"); 140 | } 141 | $code = self::$colors[$color]; 142 | } 143 | foreach ($options as $opt) { 144 | $opt = strtolower($opt); 145 | if (!isset(self::$options[$opt])) { 146 | throw new ConsoleException("Unknown option '$color'"); 147 | } 148 | $code = $code | self::$options[$opt]; 149 | } 150 | return $code; 151 | } 152 | 153 | /** 154 | * Returns a foreground color string 155 | * 156 | * @param int $color 157 | * @return string 158 | */ 159 | public static function getFgColorString($colorCode) 160 | { 161 | list($color, $options) = self::extractColorAndOptions($colorCode); 162 | $codes = array_filter(array_merge($options, array("3{$color}"))); 163 | return sprintf("\033[%sm", implode(';', $codes)); 164 | } 165 | 166 | /** 167 | * Returns a background color string 168 | * 169 | * @param int $color 170 | * @return string 171 | */ 172 | public static function getBgColorString($colorCode) 173 | { 174 | list($color, $options) = self::extractColorAndOptions($colorCode); 175 | $codes = array_filter(array_merge($options, array("4{$color}"))); 176 | return sprintf("\033[%sm", implode(';', $codes)); 177 | } 178 | 179 | /** 180 | * Extracts the options and the color from a color code 181 | * 182 | * @param int $colorCode 183 | * @return array 184 | */ 185 | private static function extractColorAndOptions($colorCode) 186 | { 187 | $options = array(); 188 | foreach (self::$options as $name => $bit) { 189 | if (($colorCode & $bit) === $bit) { 190 | $options[] = self::$codes[$bit]; 191 | $colorCode = $colorCode & ~$bit; 192 | } 193 | } 194 | if (!isset(self::$codes[$colorCode])) { 195 | throw new ConsoleException("Cannot parse color code"); 196 | } 197 | return array(self::$codes[$colorCode], $options); 198 | } 199 | 200 | public static function __callStatic($method, $args) 201 | { 202 | return self::colorize($args[0], $method); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/ConsoleKit/Command.php: -------------------------------------------------------------------------------- 1 | 22 | * class MyCommand extends Command { 23 | * public function execute(array $args, array $options = array()) { 24 | * $this->writeln('hello world'); 25 | * } 26 | * } 27 | * 28 | * 29 | * If "execute()" is not overriden, the execution will be forwarded to subcommand 30 | * methods. The subcommand name will be the first argument value and the associated 31 | * method must be prefixed with "execute". 32 | * 33 | * 34 | * class MyCommand extends Command { 35 | * public function executeSub1(array $args, array $options = array()) { 36 | * $this->writeln('hello world'); 37 | * } 38 | * public function executeSub2(array $args, array $options = array()) { 39 | * $this->writeln('hello world'); 40 | * } 41 | * } 42 | * 43 | * 44 | */ 45 | abstract class Command 46 | { 47 | /** @var Console */ 48 | protected $console; 49 | 50 | /** @var array */ 51 | protected $defaultFormatOptions = array(); 52 | 53 | /** 54 | * @param Console $console 55 | */ 56 | public function __construct(Console $console) 57 | { 58 | $this->console = $console; 59 | } 60 | 61 | /** 62 | * @return Console 63 | */ 64 | public function getConsole() 65 | { 66 | return $this->console; 67 | } 68 | 69 | /** 70 | * If not overriden, will execute the command specified 71 | * as the first argument 72 | * 73 | * Commands must be defined as methods named after the 74 | * command, prefixed with execute (eg. create -> executeCreate) 75 | * 76 | * @param array $args 77 | * @param array $options 78 | */ 79 | public function execute(array $args, array $options = array()) 80 | { 81 | if (!count($args)) { 82 | throw new ConsoleException("Missing subcommand name"); 83 | } 84 | 85 | $command = ucfirst(Utils::camelize(array_shift($args))); 86 | $methodName = "execute$command"; 87 | if (!method_exists($this, $methodName)) { 88 | throw new ConsoleException("Command '$command' does not exist"); 89 | } 90 | 91 | $method = new ReflectionMethod($this, $methodName); 92 | $params = Utils::computeFuncParams($method, $args, $options); 93 | return $method->invokeArgs($this, $params); 94 | } 95 | 96 | /** 97 | * Formats text using a {@see TextFormater} 98 | * 99 | * @param string $text 100 | * @param int|array $formatOptions Either an array of options for TextFormater or a color code 101 | * @return string 102 | */ 103 | public function format($text, $formatOptions = array()) 104 | { 105 | if (!is_array($formatOptions)) { 106 | $formatOptions = array('fgcolor' => $formatOptions); 107 | } 108 | $formatOptions = array_merge($this->defaultFormatOptions, $formatOptions); 109 | $formater = new TextFormater($formatOptions); 110 | return $formater->format($text); 111 | } 112 | 113 | /** 114 | * Executes the closure with a {@see FormatedWriter} object as the first 115 | * argument, initialized with the $formatOptions array 116 | * 117 | * 118 | * $this->context(array('quote' => ' * '), function($f) { 119 | * $f->writeln('quoted text'); 120 | * }) 121 | * 122 | * 123 | * @param array $formatOptions 124 | * @param Closure $closure 125 | */ 126 | public function context(array $formatOptions, Closure $closure) 127 | { 128 | $formater = new FormatedWriter($this->console, $formatOptions); 129 | return $closure($formater); 130 | } 131 | 132 | /** 133 | * Writes some text to the text writer 134 | * 135 | * @see format() 136 | * @param string $text 137 | * @param int|array $formatOptions 138 | * @param int $pipe 139 | * @return Command 140 | */ 141 | public function write($text, $formatOptions = array(), $pipe = TextWriter::STDOUT) 142 | { 143 | $this->console->write($this->format($text, $formatOptions), $pipe); 144 | return $this; 145 | } 146 | 147 | /** 148 | * Writes a message in bold red to STDERR 149 | * 150 | * @param string $text 151 | * @return Command 152 | */ 153 | public function writeerr($text) 154 | { 155 | return $this->write($text, Colors::RED | Colors::BOLD, TextWriter::STDERR); 156 | } 157 | 158 | /** 159 | * Writes a line of text 160 | * 161 | * @param string $text 162 | * @param int|array $formatOptions 163 | * @param int $pipe 164 | * @return Command 165 | */ 166 | public function writeln($text, $formatOptions = array(), $pipe = TextWriter::STDOUT) 167 | { 168 | $this->console->writeln($this->format($text, $formatOptions), $pipe); 169 | return $this; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/ConsoleKit/Console.php: -------------------------------------------------------------------------------- 1 | optionsParser = $parser ?: new DefaultOptionsParser(); 56 | $this->textWriter = $writer ?: new StdTextWriter(); 57 | if ($this->helpCommandClass) { 58 | $this->addCommand($this->helpCommandClass, $this->helpCommand); 59 | $this->addCommands($commands); 60 | } 61 | } 62 | 63 | /** 64 | * @param OptionsParser $parser 65 | * @return Console 66 | */ 67 | public function setOptionsParser(OptionsParser $parser) 68 | { 69 | $this->optionsParser = $parser; 70 | return $this; 71 | } 72 | 73 | /** 74 | * @return OptionsParser 75 | */ 76 | public function getOptionsParser() 77 | { 78 | return $this->optionsParser; 79 | } 80 | 81 | /** 82 | * @param TextWriter $writer 83 | * @return Console 84 | */ 85 | public function setTextWriter(TextWriter $writer) 86 | { 87 | $this->textWriter = $writer; 88 | return $this; 89 | } 90 | 91 | /** 92 | * @return TextWriter 93 | */ 94 | public function getTextWriter() 95 | { 96 | return $this->textWriter; 97 | } 98 | 99 | /** 100 | * Sets whether to call exit(1) when an exception is caught 101 | * 102 | * @param bool $exit 103 | * @return Console 104 | */ 105 | public function setExitOnException($exit = true) 106 | { 107 | $this->exitOnException = $exit; 108 | return $this; 109 | } 110 | 111 | /** 112 | * @return bool 113 | */ 114 | public function exitsOnException() 115 | { 116 | return $this->exitOnException; 117 | } 118 | 119 | /** 120 | * Sets whether a detailed error message is displayed when exception are caught 121 | * 122 | * @param boolean $enable 123 | */ 124 | public function setVerboseException($enable = true) 125 | { 126 | $this->verboseException = $enable; 127 | } 128 | 129 | /** 130 | * @return bool 131 | */ 132 | public function areExceptionsVerbose() 133 | { 134 | return $this->verboseException; 135 | } 136 | 137 | /** 138 | * Adds multiple commands at once 139 | * 140 | * @see addCommand() 141 | * @param array $commands 142 | * @return Console 143 | */ 144 | public function addCommands(array $commands) 145 | { 146 | foreach ($commands as $name => $command) { 147 | $this->addCommand($command, is_numeric($name) ? null : $name); 148 | } 149 | return $this; 150 | } 151 | 152 | /** 153 | * Registers a command 154 | * 155 | * @param callback $callback Associated class name, function name, Command instance or closure 156 | * @param string $alias Command name to be used in the shell 157 | * @param bool $default True to set the command as the default one 158 | * @return Console 159 | */ 160 | public function addCommand($callback, $alias = null, $default = false) 161 | { 162 | if ($alias instanceof \Closure && is_string($callback)) { 163 | list($alias, $callback) = array($callback, $alias); 164 | } 165 | if (is_array($callback) && is_string($callback[0])) { 166 | $callback = implode('::', $callback); 167 | } 168 | 169 | $name = ''; 170 | if (is_string($callback)) { 171 | $name = $callback; 172 | if (is_callable($callback)) { 173 | if (strpos($callback, '::') !== false) { 174 | list($classname, $methodname) = explode('::', $callback); 175 | $name = Utils::dashized($methodname); 176 | } else { 177 | $name = strtolower(trim(str_replace('_', '-', $name), '-')); 178 | } 179 | } else { 180 | if (substr($name, -7) === 'Command') { 181 | $name = substr($name, 0, -7); 182 | } 183 | $name = Utils::dashized(basename(str_replace('\\', '/', $name))); 184 | } 185 | } else if (is_object($callback) && !($callback instanceof Closure)) { 186 | $classname = get_class($callback); 187 | if (!($callback instanceof Command)) { 188 | throw new ConsoleException("'$classname' must inherit from 'ConsoleKit\Command'"); 189 | } 190 | if (substr($classname, -7) === 'Command') { 191 | $classname = substr($classname, 0, -7); 192 | } 193 | $name = Utils::dashized(basename(str_replace('\\', '/', $classname))); 194 | } else if (!$alias) { 195 | throw new ConsoleException("Commands using closures must have an alias"); 196 | } 197 | 198 | $name = $alias ?: $name; 199 | $this->commands[$name] = $callback; 200 | if ($default) { 201 | $this->defaultCommand = $name; 202 | } 203 | return $this; 204 | } 205 | 206 | /** 207 | * Registers commands from a directory 208 | * 209 | * @param string $dir 210 | * @param string $namespace 211 | * @param bool $includeFiles 212 | * @return Console 213 | */ 214 | public function addCommandsFromDir($dir, $namespace = '', $includeFiles = false) 215 | { 216 | foreach (new DirectoryIterator($dir) as $file) { 217 | $filename = $file->getFilename(); 218 | if ($file->isDir() || substr($filename, 0, 1) === '.' || strlen($filename) <= 11 219 | || strtolower(substr($filename, -11)) !== 'command.php') { 220 | continue; 221 | } 222 | if ($includeFiles) { 223 | include $file->getPathname(); 224 | } 225 | $className = trim($namespace . '\\' . substr($filename, 0, -4), '\\'); 226 | $this->addCommand($className); 227 | } 228 | return $this; 229 | } 230 | 231 | /** 232 | * @param string $name 233 | * @return bool 234 | */ 235 | public function hasCommand($name) 236 | { 237 | return isset($this->commands[$name]); 238 | } 239 | 240 | /** 241 | * @param string $name 242 | * @return string 243 | */ 244 | public function getCommand($name) 245 | { 246 | if (!isset($this->commands[$name])) { 247 | throw new ConsoleException("Command '$name' does not exist"); 248 | } 249 | return $this->commands[$name]; 250 | } 251 | 252 | /** 253 | * @return array 254 | */ 255 | public function getCommands() 256 | { 257 | return $this->commands; 258 | } 259 | 260 | /** 261 | * @param string $name 262 | * @return Console 263 | */ 264 | public function setDefaultCommand($name = null) 265 | { 266 | if ($name !== null && !isset($this->commands[$name])) { 267 | throw new ConsoleException("Command '$name' does not exist"); 268 | } 269 | $this->defaultCommand = $name; 270 | } 271 | 272 | /** 273 | * @return string 274 | */ 275 | public function getDefaultCommand() 276 | { 277 | return $this->defaultCommand; 278 | } 279 | 280 | /** 281 | * Turn off command parsing 282 | * 283 | * @param type $singleCommand 284 | * @return Console 285 | */ 286 | public function setSingleCommand($singleCommand) { 287 | $this->singleCommand = $singleCommand; 288 | return $this; 289 | } 290 | 291 | /** 292 | * @param array $args 293 | * @return mixed Results of the command callback 294 | */ 295 | public function run(array $argv = null) 296 | { 297 | try { 298 | if ($argv === null) { 299 | $argv = isset($_SERVER['argv']) ? array_slice($_SERVER['argv'], 1) : array(); 300 | } 301 | 302 | list($args, $options) = $this->getOptionsParser()->parse($argv); 303 | if($this->defaultCommand && $this->singleCommand) { 304 | return $this->execute($this->defaultCommand, $args, $options); 305 | } 306 | 307 | if (!count($args)) { 308 | if ($this->defaultCommand) { 309 | $args[] = $this->defaultCommand; 310 | } else { 311 | $this->textWriter->writeln(Colors::red("Missing command name")); 312 | $args[] = $this->helpCommand; 313 | } 314 | } 315 | 316 | $command = array_shift($args); 317 | return $this->execute($command, $args, $options); 318 | 319 | } catch (\Exception $e) { 320 | $this->writeException($e); 321 | if ($this->exitOnException) { 322 | exit(1); 323 | } 324 | throw $e; 325 | } 326 | } 327 | 328 | /** 329 | * Executes a command 330 | * 331 | * @param string $command 332 | * @param array $args 333 | * @param array $options 334 | * @return mixed 335 | */ 336 | public function execute($command = null, array $args = array(), array $options = array()) 337 | { 338 | $command = $command ?: $this->defaultCommand; 339 | if (!isset($this->commands[$command])) { 340 | throw new ConsoleException("Command '$command' does not exist"); 341 | } 342 | 343 | $callback = $this->commands[$command]; 344 | if (is_callable($callback)) { 345 | $params = array($args, $options); 346 | if (is_string($callback)) { 347 | if (strpos($callback, '::') !== false) { 348 | list($classname, $methodname) = explode('::', $callback); 349 | $reflection = new ReflectionMethod($classname, $methodname); 350 | } else { 351 | $reflection = new ReflectionFunction($callback); 352 | } 353 | $params = Utils::computeFuncParams($reflection, $args, $options); 354 | } 355 | $params[] = $this; 356 | return call_user_func_array($callback, $params); 357 | } 358 | 359 | $method = new ReflectionMethod($callback, 'execute'); 360 | $params = Utils::computeFuncParams($method, $args, $options); 361 | return $method->invokeArgs(new $callback($this), $params); 362 | } 363 | 364 | /** 365 | * Writes some text to the text writer 366 | * 367 | * @see TextWriter::write() 368 | * @param string $text 369 | * @param array $formatOptions 370 | * @return Console 371 | */ 372 | public function write($text, $pipe = TextWriter::STDOUT) 373 | { 374 | $this->textWriter->write($text, $pipe); 375 | return $this; 376 | } 377 | 378 | /** 379 | * Writes a line of text 380 | * 381 | * @see TextWriter::writeln() 382 | * @param string $text 383 | * @param array $formatOptions 384 | * @return Console 385 | */ 386 | public function writeln($text = '', $pipe = TextWriter::STDOUT) 387 | { 388 | $this->textWriter->writeln($text, $pipe); 389 | return $this; 390 | } 391 | 392 | /** 393 | * Writes an error message to stderr 394 | * 395 | * @param \Exception $e 396 | * @return Console 397 | */ 398 | public function writeException(\Exception $e) 399 | { 400 | if ($this->verboseException) { 401 | $text = sprintf("[%s]\n%s\nIn %s at line %s\n%s", 402 | get_class($e), 403 | $e->getMessage(), 404 | $e->getFile(), 405 | $e->getLine(), 406 | $e->getTraceAsString() 407 | ); 408 | } else { 409 | $text = sprintf("\n[%s]\n%s\n", get_class($e), $e->getMessage()); 410 | } 411 | 412 | $box = new Widgets\Box($this->textWriter, $text, ''); 413 | $out = Colors::colorizeLines($box, Colors::WHITE, Colors::RED); 414 | $out = TextFormater::apply($out, array('indent' => 2)); 415 | $this->textWriter->writeln($out); 416 | return $this; 417 | } 418 | } 419 | -------------------------------------------------------------------------------- /src/ConsoleKit/ConsoleException.php: -------------------------------------------------------------------------------- 1 | write("$text\n", $pipe); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/ConsoleKit/FileSystem.php: -------------------------------------------------------------------------------- 1 | textWriter = $writer; 29 | } 30 | 31 | /** 32 | * @param TextWriter $writer 33 | * @return FormatedWriter 34 | */ 35 | public function setTextWriter(TextWriter $writer) 36 | { 37 | $this->textWriter = $writer; 38 | return $this; 39 | } 40 | 41 | /** 42 | * @return TextWriter 43 | */ 44 | public function getTextWriter() 45 | { 46 | return $this->textWriter; 47 | } 48 | 49 | /** 50 | * Writes some text to the text writer 51 | * 52 | * @see TextWriter::write() 53 | * @param string $text 54 | * @param array $formatOptions 55 | * @return Command 56 | */ 57 | public function write($text, $pipe = TextWriter::STDOUT) 58 | { 59 | $this->textWriter->write($this->format($text), $pipe); 60 | return $this; 61 | } 62 | 63 | /** 64 | * Writes a line of text 65 | * 66 | * @see TextWriter::writeln() 67 | * @param string $text 68 | * @param array $formatOptions 69 | * @return Command 70 | */ 71 | public function writeln($text = '', $pipe = TextWriter::STDOUT) 72 | { 73 | $this->textWriter->writeln($this->format($text), $pipe); 74 | return $this; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/ConsoleKit/Help.php: -------------------------------------------------------------------------------- 1 | getDocComment()); 67 | } 68 | 69 | /** 70 | * Creates an Help object from a class subclassing Command 71 | * 72 | * @param string $name 73 | * @param string $subCommand 74 | * @return Help 75 | */ 76 | public static function fromCommandClass($name, $subCommand = null) 77 | { 78 | $prefix = 'execute'; 79 | $class = new ReflectionClass($name); 80 | 81 | if ($subCommand) { 82 | $method = $prefix . ucfirst(Utils::camelize($subCommand)); 83 | if (!$class->hasMethod($method)) { 84 | throw new ConsoleException("Sub command '$subCommand' of '$name' does not exist"); 85 | } 86 | return new Help($class->getMethod($method)->getDocComment()); 87 | } 88 | 89 | $help = new Help($class->getDocComment()); 90 | foreach ($class->getMethods() as $method) { 91 | if (strlen($method->getName()) > strlen($prefix) && 92 | substr($method->getName(), 0, strlen($prefix)) === $prefix) { 93 | $help->subCommands[] = Utils::dashized(substr($method->getName(), strlen($prefix))); 94 | } 95 | } 96 | return $help; 97 | } 98 | 99 | /** 100 | * @param string $text 101 | */ 102 | protected function __construct($text = '') 103 | { 104 | $this->text = $text; 105 | $this->parse(); 106 | } 107 | 108 | protected function parse() 109 | { 110 | $this->usage = ''; 111 | $this->args = array(); 112 | $this->options = array(); 113 | $this->flags = array(); 114 | 115 | $lines = explode("\n", substr(trim($this->text), 2, -2)); 116 | $lines = array_map(function($v) { return ltrim(trim($v), '* '); }, $lines); 117 | 118 | $desc = array(); 119 | foreach ($lines as $line) { 120 | if (preg_match('/@usage (.+)$/', $line, $matches)) { 121 | $this->usage = $matches[1]; 122 | } else if (preg_match('/@arg ([^\s]+)( (.*)|)$/', $line, $matches)) { 123 | $this->args[$matches[1]] = isset($matches[3]) ? $matches[3] : ''; 124 | } else if (preg_match('/@opt ([a-zA-Z\-_0-9=]+)( (.*)|)$/', $line, $matches)) { 125 | $this->options[$matches[1]] = isset($matches[3]) ? $matches[3] : ''; 126 | } else if (preg_match('/@flag ([a-zA-Z0-9])( (.*)|)$/', $line, $matches)) { 127 | $this->flags[$matches[1]] = isset($matches[3]) ? $matches[3] : ''; 128 | } else if (!preg_match('/^@([a-zA-Z\-_0-9]+)(.*)$/', $line)) { 129 | $desc[] = $line; 130 | } 131 | } 132 | 133 | $this->description = trim(implode("\n", $desc), "\n "); 134 | } 135 | 136 | /** 137 | * @return string 138 | */ 139 | public function getDescrition() 140 | { 141 | return $this->description; 142 | } 143 | 144 | /** 145 | * @return string 146 | */ 147 | public function getUsage() 148 | { 149 | return $this->usage; 150 | } 151 | 152 | /** 153 | * @return array 154 | */ 155 | public function getArgs() 156 | { 157 | return $this->args; 158 | } 159 | 160 | /** 161 | * @return array 162 | */ 163 | public function getOptions() 164 | { 165 | return $this->options; 166 | } 167 | 168 | /** 169 | * @return array 170 | */ 171 | public function getFlags() 172 | { 173 | return $this->flags; 174 | } 175 | 176 | /** 177 | * @return bool 178 | */ 179 | public function hasSubCommands() 180 | { 181 | return !empty($this->subCommands); 182 | } 183 | 184 | /** 185 | * @return array 186 | */ 187 | public function getSubCommands() 188 | { 189 | return $this->subCommands; 190 | } 191 | 192 | /** 193 | * @return string 194 | */ 195 | public function render() 196 | { 197 | $output = "{$this->description}\n\n"; 198 | if (!empty($this->usage)) { 199 | $output .= "Usage: {$this->usage}\n\n"; 200 | } 201 | if (!empty($this->args)) { 202 | $output .= Colors::colorize("Arguments:\n", Colors::BLACK | Colors::BOLD); 203 | foreach ($this->args as $name => $desc) { 204 | $output .= sprintf(" %s\t%s\n", $name, $desc); 205 | } 206 | $output .= "\n"; 207 | } 208 | if (!empty($this->options)) { 209 | $output .= Colors::colorize("Available options:\n", Colors::BLACK | Colors::BOLD); 210 | foreach ($this->options as $name => $desc) { 211 | $output .= sprintf(" --%s\t%s\n", $name, $desc); 212 | } 213 | $output .= "\n"; 214 | } 215 | if (!empty($this->flags)) { 216 | $output .= Colors::colorize("Available flags:\n", Colors::BLACK | Colors::BOLD); 217 | foreach ($this->flags as $name => $desc) { 218 | $output .= sprintf(" -%s\t%s\n", $name, $desc); 219 | } 220 | $output .= "\n"; 221 | } 222 | if (!empty($this->subCommands)) { 223 | $output .= Colors::colorize("Available sub commands:\n", Colors::BLACK | Colors::BOLD); 224 | foreach ($this->subCommands as $name) { 225 | $output .= " - $name\n"; 226 | } 227 | $output .= "\n"; 228 | } 229 | return trim($output, "\n "); 230 | } 231 | 232 | public function __toString() 233 | { 234 | return $this->render(); 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/ConsoleKit/HelpCommand.php: -------------------------------------------------------------------------------- 1 | ' * ')); 19 | $this->writeln('Available commands:', Colors::BLACK | Colors::BOLD); 20 | foreach ($this->console->getCommands() as $name => $fqdn) { 21 | if ($fqdn !== __CLASS__) { 22 | $this->writeln($formater->format($name)); 23 | } 24 | } 25 | $scriptName = basename($_SERVER['SCRIPT_FILENAME']); 26 | $this->writeln("Use './$scriptName help command' for more info"); 27 | } else { 28 | $commandFQDN = $this->console->getCommand($args[0]); 29 | $help = Help::fromFQDN($commandFQDN, Utils::get($args, 1)); 30 | $this->writeln($help); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ConsoleKit/OptionsParser.php: -------------------------------------------------------------------------------- 1 | write("$text\n", $pipe); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/ConsoleKit/TextFormater.php: -------------------------------------------------------------------------------- 1 | format($text); 47 | } 48 | 49 | /** 50 | * @param array $options 51 | */ 52 | public function __construct(array $options = array()) 53 | { 54 | $this->indentWidth = self::$defaultIndentWidth; 55 | $this->setOptions($options); 56 | } 57 | 58 | /** 59 | * Available options: 60 | * - indentWidth 61 | * - indent 62 | * - quote 63 | * - fgcolor 64 | * - bgcolor 65 | * 66 | * @param array $options 67 | * @return TextFormater 68 | */ 69 | public function setOptions(array $options) 70 | { 71 | if (isset($options['indentWidth'])) { 72 | $this->setIndentWidth($options['indentWidth']); 73 | } 74 | if (isset($options['indent'])) { 75 | $this->setIndent($options['indent']); 76 | } 77 | if (isset($options['quote'])) { 78 | $this->setQuote($options['quote']); 79 | } 80 | if (isset($options['fgcolor'])) { 81 | $this->setFgColor($options['fgcolor']); 82 | } 83 | if (isset($options['bgcolor'])) { 84 | $this->setBgColor($options['bgcolor']); 85 | } 86 | return $this; 87 | } 88 | 89 | /** 90 | * @param int $indent 91 | * @return TextFormater 92 | */ 93 | public function setIndentWidth($width) 94 | { 95 | $this->indentWidth = (int) $width; 96 | return $this; 97 | } 98 | 99 | /** 100 | * @return int 101 | */ 102 | public function getIndentWidth() 103 | { 104 | return $this->indentWidth; 105 | } 106 | 107 | /** 108 | * @param int $indent 109 | * @return TextFormater 110 | */ 111 | public function setIndent($indent) 112 | { 113 | $this->indent = (int) $indent; 114 | return $this; 115 | } 116 | 117 | /** 118 | * @return int 119 | */ 120 | public function getIndent() 121 | { 122 | return $this->indent; 123 | } 124 | 125 | /** 126 | * @param string $quote 127 | * @return TextFormater 128 | */ 129 | public function setQuote($quote) 130 | { 131 | $this->quote = $quote; 132 | return $this; 133 | } 134 | 135 | /** 136 | * @return string 137 | */ 138 | public function getQuote() 139 | { 140 | return $this->quote; 141 | } 142 | 143 | /** 144 | * @param string $color 145 | * @return TextFormater 146 | */ 147 | public function setFgColor($color) 148 | { 149 | $this->fgColor = $color; 150 | return $this; 151 | } 152 | 153 | /** 154 | * @return string 155 | */ 156 | public function getFgColor() 157 | { 158 | return $this->fgColor; 159 | } 160 | 161 | /** 162 | * @param string $color 163 | * @return TextFormater 164 | */ 165 | public function setBgColor($color) 166 | { 167 | $this->bgColor = $color; 168 | return $this; 169 | } 170 | 171 | /** 172 | * @return string 173 | */ 174 | public function getBgColor() 175 | { 176 | return $this->bgColor; 177 | } 178 | 179 | /** 180 | * Formats $text according to the formater's options 181 | * 182 | * @param string $text 183 | */ 184 | public function format($text) 185 | { 186 | $lines = explode("\n", $text); 187 | foreach ($lines as &$line) { 188 | $line = ((string) $this->quote) 189 | . str_repeat(' ', $this->indent * $this->indentWidth) 190 | . $line; 191 | } 192 | return Colors::colorize(implode("\n", $lines), $this->fgColor, $this->bgColor); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/ConsoleKit/TextWriter.php: -------------------------------------------------------------------------------- 1 | getDocComment())) { 160 | return array($args, $options); 161 | } 162 | 163 | $nbRequiredParams = $reflection->getNumberOfRequiredParameters(); 164 | if (count($args) < $nbRequiredParams) { 165 | throw new ConsoleException("Not enough parameters in '" . $reflection->getName() . "'"); 166 | } 167 | 168 | $params = $args; 169 | if (count($args) > $nbRequiredParams) { 170 | $params = array_slice($args, 0, $nbRequiredParams); 171 | $args = array_slice($args, $nbRequiredParams); 172 | } 173 | 174 | foreach ($reflection->getParameters() as $param) { 175 | if ($param->isOptional() && substr($param->getName(), 0, 1) !== '_') { 176 | if (array_key_exists($param->getName(), $options)) { 177 | $params[] = $options[$param->getName()]; 178 | unset($options[$param->getName()]); 179 | } else { 180 | $params[] = $param->getDefaultValue(); 181 | } 182 | } 183 | } 184 | 185 | $params[] = $args; 186 | $params[] = $options; 187 | return $params; 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/ConsoleKit/Widgets/AbstractWidget.php: -------------------------------------------------------------------------------- 1 | textWriter = $writer; 26 | } 27 | 28 | /** 29 | * @param TextWriter $writer 30 | * @return Dialog 31 | */ 32 | public function setTextWriter(TextWriter $writer) 33 | { 34 | $this->textWriter = $writer; 35 | return $this; 36 | } 37 | 38 | /** 39 | * @return TextWriter 40 | */ 41 | public function getTextWriter() 42 | { 43 | return $this->textWriter; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/ConsoleKit/Widgets/Box.php: -------------------------------------------------------------------------------- 1 | textWriter = $writer; 34 | $this->text = $text; 35 | $this->lineCharacter = $lineCharacter; 36 | $this->padding = $padding; 37 | } 38 | 39 | /** 40 | * @param string text 41 | * @return Box 42 | */ 43 | public function setText($text) 44 | { 45 | $this->text = $text; 46 | return $this; 47 | } 48 | 49 | /** 50 | * @return string 51 | */ 52 | public function getText() 53 | { 54 | return $this->text; 55 | } 56 | 57 | /** 58 | * @param string lineCharacter 59 | * @return Box 60 | */ 61 | public function setLineCharacter($lineCharacter) 62 | { 63 | $this->lineCharacter = $lineCharacter; 64 | return $this; 65 | } 66 | 67 | /** 68 | * @return string 69 | */ 70 | public function getLineCharacter() 71 | { 72 | return $this->lineCharacter; 73 | } 74 | 75 | /** 76 | * @param int padding 77 | * @return Box 78 | */ 79 | public function setPadding($padding) 80 | { 81 | $this->padding = $padding; 82 | return $this; 83 | } 84 | 85 | /** 86 | * @return int 87 | */ 88 | public function getPadding() 89 | { 90 | return $this->padding; 91 | } 92 | 93 | /** 94 | * @return string 95 | */ 96 | public function render() 97 | { 98 | $lines = explode("\n", $this->text); 99 | $maxWidth = 0; 100 | foreach ($lines as $line) { 101 | if (strlen($line) > $maxWidth) { 102 | $maxWidth = strlen($line); 103 | } 104 | } 105 | 106 | $maxWidth += $this->padding * 2 + 2; 107 | $c = $this->lineCharacter; 108 | $output = str_repeat($c, $maxWidth) . "\n"; 109 | foreach ($lines as $line) { 110 | $delta = $maxWidth - (strlen($line) + 2 + $this->padding * 2); 111 | $output .= $c . str_repeat(' ', $this->padding) . $line 112 | . str_repeat(' ', $delta + $this->padding) . $c . "\n"; 113 | } 114 | $output .= str_repeat($c, $maxWidth); 115 | return $output; 116 | } 117 | 118 | public function write() 119 | { 120 | if ($this->textWriter === null) { 121 | throw new ConsoleException('No TextWriter object specified'); 122 | } 123 | $this->textWriter->write($this->render()); 124 | return $this; 125 | } 126 | 127 | public function __toString() 128 | { 129 | return $this->render(); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/ConsoleKit/Widgets/Checklist.php: -------------------------------------------------------------------------------- 1 | 19 | * $checklist = new ConsoleKit\Widgets\Checklist($console); 20 | * $checklist->run(array( 21 | * 'Creating directory structure' => function() { 22 | * // code 23 | * return $success; // bool 24 | * }, 25 | * 'Downloading scripts' => function() { 26 | * // code 27 | * return $success; // bool 28 | * } 29 | * )); 30 | * 31 | * 32 | * Will print: 33 | * 34 | * Creating directory structure OK 35 | * Downloading scripts OK 36 | */ 37 | class Checklist extends AbstractWidget 38 | { 39 | protected $maxMessageLength = 100; 40 | 41 | protected $successText = 'OK'; 42 | 43 | protected $successColor = Colors::GREEN; 44 | 45 | protected $errorText = 'FAIL'; 46 | 47 | protected $errorColor = Colors::RED; 48 | 49 | public function setMaxMessageLength($length) 50 | { 51 | $this->maxMessageLength = $length; 52 | } 53 | 54 | public function getMaxMessageLength() 55 | { 56 | return $this->maxMessageLength; 57 | } 58 | 59 | public function setSuccessText($text, $color = Colors::GREEN) 60 | { 61 | $this->successText = $text; 62 | $this->successColor = $color; 63 | } 64 | 65 | public function setErrorText($text, $color = Colors::RED) 66 | { 67 | $this->errorText = $text; 68 | $this->errorColor = $color; 69 | } 70 | 71 | public function run(array $steps) 72 | { 73 | $maxMessageLength = min(array_reduce(array_keys($steps), function($r, $i) { 74 | return max(strlen($i), $r); 75 | }, 0), $this->maxMessageLength); 76 | 77 | foreach ($steps as $message => $callback) { 78 | $this->step($message, $callback, $maxMessageLength); 79 | } 80 | } 81 | 82 | public function runArray($array, $message, $callback, $useKeyInMessage = false) 83 | { 84 | $steps = array(); 85 | foreach ($array as $k => $v) { 86 | $steps[sprintf($message, $useKeyInMessage ? $k : $v)] = function() use ($k, $v) { 87 | return $callback($v, $k); 88 | }; 89 | } 90 | return $this->run($steps); 91 | } 92 | 93 | public function step($message, $callback, $maxMessageLength = null) 94 | { 95 | $maxMessageLength = $maxMessageLength ?: $this->maxMessageLength; 96 | $this->textWriter->write(sprintf("%-${maxMessageLength}s", $message)); 97 | if (call_user_func($callback)) { 98 | $this->textWriter->write(Colors::colorize($this->successText, $this->successColor)); 99 | } else { 100 | $this->textWriter->write(Colors::colorize($this->errorText, $this->errorColor)); 101 | } 102 | $this->textWriter->write("\n"); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/ConsoleKit/Widgets/Dialog.php: -------------------------------------------------------------------------------- 1 | 30) { 28 | $defaultText = substr($default, 0, 30) . '...'; 29 | } 30 | $text .= " [$defaultText]"; 31 | } 32 | $this->textWriter->write("$text "); 33 | return trim(fgets(STDIN)) ?: $default; 34 | } 35 | 36 | /** 37 | * Writes $text (followed by the list of choices) and reads the user response. 38 | * Returns true if it matches $expected, false otherwise 39 | * 40 | * 41 | * if($dialog->confirm('Are you sure?')) { ... } 42 | * if($dialog->confirm('Your choice?', null, array('a', 'b', 'c'))) { ... } 43 | * 44 | * 45 | * @param string $text 46 | * @param string $expected 47 | * @param array $choices 48 | * @param string $default 49 | * @param string $errorMessage 50 | * @return bool 51 | */ 52 | public function confirm($text, $expected = 'y', array $choices = array('Y', 'n'), $default = 'y', $errorMessage = 'Invalid choice') 53 | { 54 | $text = $text . ' [' . implode('/', $choices) . ']'; 55 | $choices = array_map('strtolower', $choices); 56 | $expected = strtolower($expected); 57 | $default = strtolower($default); 58 | do { 59 | $input = strtolower($this->ask($text)); 60 | if (in_array($input, $choices)) { 61 | return $input === $expected; 62 | } else if (empty($input) && !empty($default)) { 63 | return $default === $expected; 64 | } 65 | $this->textWriter->writeln($errorMessage); 66 | } while (true); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/ConsoleKit/Widgets/ProgressBar.php: -------------------------------------------------------------------------------- 1 | 20 | * $total = 100; 21 | * $progress = new ProgressBar($textWriter, $total); 22 | * for ($i = 0; $i < $total; $i++) { 23 | * $progress->incr(); 24 | * usleep(10000); 25 | * } 26 | * $progress->stop(); 27 | * 28 | */ 29 | class ProgressBar extends AbstractWidget 30 | { 31 | /** @var int */ 32 | protected $value = 0; 33 | 34 | /** @var int */ 35 | protected $total = 0; 36 | 37 | /** @var int */ 38 | protected $size = 0; 39 | 40 | /** @var bool */ 41 | protected $showRemainingTime = true; 42 | 43 | /** @var int */ 44 | protected $startTime; 45 | 46 | /** 47 | * @param TextWriter $writer 48 | * @param int $total 49 | * @param int $size 50 | */ 51 | public function __construct(TextWriter $writer = null, $total = 100, $size = 50, $showRemainingTime = true) 52 | { 53 | $this->textWriter = $writer; 54 | $this->size = $size; 55 | $this->showRemainingTime = $showRemainingTime; 56 | $this->start($total); 57 | } 58 | 59 | /** 60 | * @param int $size 61 | * @return ProgressBar 62 | */ 63 | public function setSize($size) 64 | { 65 | $this->size = $size; 66 | return $this; 67 | } 68 | 69 | /** 70 | * @return int 71 | */ 72 | public function getSize() 73 | { 74 | return $this->size; 75 | } 76 | 77 | /** 78 | * @param bool $show 79 | */ 80 | public function setShowRemainingTime($show = true) 81 | { 82 | $this->showRemainingTime = $show; 83 | } 84 | 85 | /** 86 | * @return bool 87 | */ 88 | public function getShowRemainingTime() 89 | { 90 | return $this->showRemainingTime; 91 | } 92 | 93 | /** 94 | * @param number $value 95 | */ 96 | public function setValue($value) 97 | { 98 | $this->value = $value; 99 | } 100 | 101 | /** 102 | * @return number 103 | */ 104 | public function getValue() 105 | { 106 | return $this->value; 107 | } 108 | 109 | /** 110 | * @param int $total 111 | * @return ProgressBar 112 | */ 113 | public function start($total = 100) 114 | { 115 | $this->value = 0; 116 | $this->total = $total; 117 | $this->startTime = time(); 118 | return $this; 119 | } 120 | 121 | /** 122 | * Increments the value and calls {@see write()} 123 | * 124 | * @param int $increment 125 | * @return ProgressBar 126 | */ 127 | public function incr($increment = 1) 128 | { 129 | $this->value += $increment; 130 | $this->write(); 131 | return $this; 132 | } 133 | 134 | /** 135 | * Writes a new line 136 | * 137 | * @return ProgressBar 138 | */ 139 | public function stop() 140 | { 141 | $this->textWriter->writeln(); 142 | return $this; 143 | } 144 | 145 | /** 146 | * Generates the text to write for the current values 147 | * 148 | * @return string 149 | */ 150 | public function render() 151 | { 152 | $percentage = (double) ($this->value / $this->total); 153 | 154 | $progress = floor($percentage * $this->size); 155 | $output = "\r[" . str_repeat('=', $progress); 156 | if ($progress < $this->size) { 157 | $output .= ">" . str_repeat(' ', $this->size - $progress); 158 | } else { 159 | $output .= '='; 160 | } 161 | $output .= sprintf('] %s%% %s/%s', round($percentage * 100, 0), $this->value, $this->total); 162 | 163 | if ($this->showRemainingTime) { 164 | $speed = (time() - $this->startTime) / $this->value; 165 | $remaining = number_format(round($speed * ($this->total - $this->value), 2), 2); 166 | $output .= " - $remaining sec remaining"; 167 | } 168 | 169 | return $output; 170 | } 171 | 172 | /** 173 | * Writes the rendered progress bar to the text writer 174 | * 175 | * @return ProgressBar 176 | */ 177 | public function write() 178 | { 179 | if ($this->textWriter === null) { 180 | throw new ConsoleException('No TextWriter object specified'); 181 | } 182 | $this->textWriter->write($this->render()); 183 | return $this; 184 | } 185 | 186 | public function __toString() 187 | { 188 | return $this->render(); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /tests/ConsoleKit/Tests/ColorsTest.php: -------------------------------------------------------------------------------- 1 | assertEquals("\033[31mred\033[0m", Colors::colorize('red', Colors::RED)); 12 | $this->assertEquals("\033[1;31mred\033[0m", Colors::colorize('red', Colors::RED | Colors::BOLD)); 13 | $this->assertEquals("\033[43m\033[31mred\033[0m", Colors::colorize('red', Colors::RED, Colors::YELLOW)); 14 | $this->assertEquals("\033[31mred\033[0m", Colors::red('red')); 15 | } 16 | 17 | public function testGetColorCode() 18 | { 19 | $this->assertEquals(Colors::RED, Colors::getColorCode(Colors::RED)); 20 | $this->assertEquals(Colors::RED, Colors::getColorCode('red')); 21 | $this->assertEquals(Colors::GREEN, Colors::getColorCode('GREEN')); 22 | } 23 | 24 | public function testGetBoldColorCode() 25 | { 26 | $this->assertEquals(Colors::YELLOW | Colors::BOLD, Colors::getColorCode(Colors::YELLOW | Colors::BOLD)); 27 | $this->assertEquals(Colors::RED | Colors::BOLD, Colors::getColorCode('red+bold')); 28 | $this->assertEquals(Colors::GREEN | Colors::BOLD, Colors::getColorCode('green', array('bold'))); 29 | } 30 | 31 | /** 32 | * @expectedException ConsoleKit\ConsoleException 33 | * @expectedExceptionMessage Unknown color 'foobar' 34 | */ 35 | public function testGetUnknownColorCode() 36 | { 37 | Colors::getColorCode('foobar'); 38 | } 39 | 40 | public function testGetFgColorString() 41 | { 42 | $this->assertEquals("\033[34m", Colors::getFgColorString(Colors::BLUE)); 43 | $this->assertEquals("\033[1;34m", Colors::getFgColorString(Colors::BLUE | Colors::BOLD)); 44 | } 45 | 46 | public function testGetBgColorString() 47 | { 48 | $this->assertEquals("\033[45m", Colors::getBgColorString(Colors::MAGENTA)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/ConsoleKit/Tests/ConsoleKitTestCase.php: -------------------------------------------------------------------------------- 1 | console = new Console(); 15 | $this->console->setTextWriter(new EchoTextWriter()); 16 | $this->console->setExitOnException(false); 17 | } 18 | 19 | public function testAddCommand() 20 | { 21 | $this->console->addCommand('ConsoleKit\Tests\TestCommand'); 22 | $this->assertArrayHasKey('test', $this->console->getCommands()); 23 | $this->assertEquals('ConsoleKit\Tests\TestCommand', $this->console->getCommand('test')); 24 | 25 | $this->console->addCommand('ConsoleKit\Tests\TestCommand', 'test-alias'); 26 | $this->assertArrayHasKey('test-alias', $this->console->getCommands()); 27 | $this->assertEquals('ConsoleKit\Tests\TestCommand', $this->console->getCommand('test-alias')); 28 | 29 | $this->console->addCommand('ConsoleKit\Tests\TestStatic::sayHello'); 30 | $this->assertArrayHasKey('say-hello', $this->console->getCommands()); 31 | $this->assertEquals('ConsoleKit\Tests\TestStatic::sayHello', $this->console->getCommand('say-hello')); 32 | 33 | $this->console->addCommand('var_dump'); 34 | $this->assertArrayHasKey('var-dump', $this->console->getCommands()); 35 | $this->assertEquals('var_dump', $this->console->getCommand('var-dump')); 36 | 37 | $this->console->addCommand(array(new TestCommand($this->console), 'execute'), 'test-callback'); 38 | $this->assertArrayHasKey('test-callback', $this->console->getCommands()); 39 | $this->assertInternalType('array', $this->console->getCommand('test-callback')); 40 | 41 | $this->console->addCommand(function($args, $opts) { echo 'hello!'; }, 'hello'); 42 | $this->assertArrayHasKey('hello', $this->console->getCommands()); 43 | $this->assertInstanceOf('Closure', $this->console->getCommand('hello')); 44 | } 45 | 46 | public function testAddCommands() 47 | { 48 | $this->console->addCommands(array( 49 | 'ConsoleKit\Tests\TestCommand', 50 | 'test-alias' => 'ConsoleKit\Tests\TestCommand' 51 | )); 52 | 53 | $this->assertArrayHasKey('test', $this->console->getCommands()); 54 | $this->assertEquals('ConsoleKit\Tests\TestCommand', $this->console->getCommand('test')); 55 | $this->assertArrayHasKey('test-alias', $this->console->getCommands()); 56 | $this->assertEquals('ConsoleKit\Tests\TestCommand', $this->console->getCommand('test-alias')); 57 | } 58 | 59 | public function testAddCommandsFromDir() 60 | { 61 | $this->console->addCommandsFromDir(__DIR__, 'ConsoleKit\Tests'); 62 | $this->assertArrayHasKey('test', $this->console->getCommands()); 63 | $this->assertEquals('ConsoleKit\Tests\TestCommand', $this->console->getCommand('test')); 64 | } 65 | 66 | public function testExecute() 67 | { 68 | $this->expectOutputString("hello unknown!\n"); 69 | $this->console->addCommand('ConsoleKit\Tests\TestCommand'); 70 | $this->console->execute('test'); 71 | } 72 | 73 | public function testExecuteWithArgs() 74 | { 75 | $this->expectOutputString("hello foo bar!\n"); 76 | $this->console->addCommand('ConsoleKit\Tests\TestCommand'); 77 | $this->console->execute('test', array('foo', 'bar')); 78 | } 79 | 80 | public function testExecuteWithOption() 81 | { 82 | $this->expectOutputString("hello foobar!\n"); 83 | $this->console->addCommand('ConsoleKit\Tests\TestCommand'); 84 | $this->console->execute('test', array(), array('name' => 'foobar')); 85 | } 86 | 87 | public function testExecuteSubCommand() 88 | { 89 | $this->console->addCommand('ConsoleKit\Tests\TestSubCommand', 'test'); 90 | $this->assertEquals('hello foobar!', $this->console->execute('test', array('say-hello', 'foobar'))); 91 | $this->assertEquals('hi foobar!', $this->console->execute('test', array('say-hi', 'foobar'))); 92 | } 93 | 94 | public function testExecuteFunction() 95 | { 96 | $this->expectOutputString("\033[31mhello foobar!\033[0m\n"); 97 | $this->console->addCommand(function($args, $opts, $console) { 98 | $console->writeln(Colors::colorize(sprintf("hello %s!", $args[0]), $opts['color'])); 99 | }, 'test'); 100 | $this->console->addCommand('test2', function($args, $opts, $console) { 101 | return "success"; 102 | }); 103 | $this->console->execute('test', array('foobar'), array('color' => 'red')); 104 | $this->assertEquals("success", $this->console->execute('test2')); 105 | } 106 | 107 | public function testRun() 108 | { 109 | $this->expectOutputString("hello unknown!\n"); 110 | $this->console->addCommand('ConsoleKit\Tests\TestCommand'); 111 | $this->console->run(array('test')); 112 | } 113 | 114 | public function testDefaultCommand() 115 | { 116 | $this->expectOutputString("hello unknown!\n"); 117 | $this->console->addCommand('ConsoleKit\Tests\TestCommand', null, true); 118 | $this->console->run(array()); 119 | } 120 | 121 | public function testOneCommandWithArguments() { 122 | $this->expectOutputString("hello foobar!\n"); 123 | $this->console->addCommand('ConsoleKit\Tests\TestCommand', null, true); 124 | $this->console->setSingleCommand(true); 125 | $this->console->run(array('foobar')); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /tests/ConsoleKit/Tests/DefautOptionsParserTest.php: -------------------------------------------------------------------------------- 1 | parse(array('arg1')); 14 | $this->assertContains('arg1', $args); 15 | $this->assertEmpty($options); 16 | 17 | list($args, $options) = $parser->parse(array('arg1', 'arg2')); 18 | $this->assertContains('arg1', $args); 19 | $this->assertContains('arg2', $args); 20 | $this->assertEmpty($options); 21 | 22 | list($args, $options) = $parser->parse(array('--foo', 'arg1')); 23 | $this->assertContains('arg1', $args); 24 | $this->assertArrayHasKey('foo', $options); 25 | 26 | list($args, $options) = $parser->parse(array('--foo=bar', '--foobar')); 27 | $this->assertCount(2, $options); 28 | $this->assertArrayHasKey('foo', $options); 29 | $this->assertEquals('bar', $options['foo']); 30 | $this->assertArrayHasKey('foobar', $options); 31 | 32 | list($args, $options) = $parser->parse(array('--foo=bar', '--foo=baz')); 33 | $this->assertArrayHasKey('foo', $options); 34 | $this->assertInternalType('array', $options['foo']); 35 | $this->assertContains('bar', $options['foo']); 36 | $this->assertContains('baz', $options['foo']); 37 | 38 | list($args, $options) = $parser->parse(array('-a', '-bc')); 39 | $this->assertCount(3, $options); 40 | $this->assertArrayHasKey('a', $options); 41 | $this->assertArrayHasKey('b', $options); 42 | $this->assertArrayHasKey('c', $options); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/ConsoleKit/Tests/TestCommand.php: -------------------------------------------------------------------------------- 1 | writeln(sprintf("hello %s!", $name)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/ConsoleKit/Tests/TestStatic.php: -------------------------------------------------------------------------------- 1 | writeln(sprintf("hello %s!", $name)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/ConsoleKit/Tests/TestSubCommand.php: -------------------------------------------------------------------------------- 1 | formater = new TextFormater(); 12 | $this->formater->setOptions(array( 13 | 'indentWidth' => 4, 14 | 'indent' => 1, 15 | 'quote' => '>', 16 | 'fgcolor' => 'black', 17 | 'bgcolor' => 'white' 18 | )); 19 | } 20 | 21 | public function testSetOptions() 22 | { 23 | $this->assertEquals(4, $this->formater->getIndentWidth()); 24 | $this->assertEquals(1, $this->formater->getIndent()); 25 | $this->assertEquals('>', $this->formater->getQuote()); 26 | $this->assertEquals('black', $this->formater->getFgColor()); 27 | $this->assertEquals('white', $this->formater->getBgColor()); 28 | } 29 | 30 | public function testFormat() 31 | { 32 | $expected = "\033[47m\033[30m> my text\033[0m"; 33 | $this->assertEquals($expected, $this->formater->format('my text')); 34 | } 35 | 36 | public function testFormatMultiline() 37 | { 38 | $expected = "\033[47m\033[30m> line1\n> line2\033[0m"; 39 | $this->assertEquals($expected, $this->formater->format("line1\nline2")); 40 | } 41 | } -------------------------------------------------------------------------------- /tests/ConsoleKit/Tests/UtilsTest.php: -------------------------------------------------------------------------------- 1 | 'bar'); 12 | $this->assertEquals('bar', Utils::get($data, 'foo')); 13 | $this->assertNull(Utils::get($data, 'unknown')); 14 | $this->assertEquals('default', Utils::get($data, 'unknown', 'default')); 15 | } 16 | 17 | public function testFind() 18 | { 19 | $this->assertEquals(realpath(__FILE__), Utils::find(basename(__FILE__), __DIR__)); 20 | } 21 | 22 | public function testFilterFiles() 23 | { 24 | $this->assertEquals(array(__FILE__), Utils::filterFiles(array(__FILE__, 'not_existant_file'))); 25 | } 26 | 27 | public function testJoin() 28 | { 29 | $path = "foo" . DIRECTORY_SEPARATOR . "bar"; 30 | $this->assertEquals($path, Utils::join('foo', 'bar')); 31 | } 32 | 33 | public function testCamelize() 34 | { 35 | $this->assertEquals('fooBar', Utils::camelize('foo-bar')); 36 | } 37 | 38 | public function testDashized() 39 | { 40 | $this->assertEquals('foo-bar', Utils::dashized('fooBar')); 41 | } 42 | } -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 |