PageRenderTime 39ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/cli/Streams.php

http://github.com/jlogsdon/php-cli-tools
PHP | 256 lines | 116 code | 29 blank | 111 comment | 25 complexity | 5815e64a6a56a750def651ae2154aa5b MD5 | raw file
  1. <?php
  2. namespace cli;
  3. class Streams {
  4. protected static $out = STDOUT;
  5. protected static $in = STDIN;
  6. protected static $err = STDERR;
  7. /**
  8. * Handles rendering strings. If extra scalar arguments are given after the `$msg`
  9. * the string will be rendered with `sprintf`. If the second argument is an `array`
  10. * then each key in the array will be the placeholder name. Placeholders are of the
  11. * format {:key}.
  12. *
  13. * @param string $msg The message to render.
  14. * @param mixed ... Either scalar arguments or a single array argument.
  15. * @return string The rendered string.
  16. */
  17. public static function render( $msg ) {
  18. $args = func_get_args();
  19. // No string replacement is needed
  20. if( count( $args ) == 1 ) {
  21. return Colors::colorize( $msg );
  22. }
  23. // If the first argument is not an array just pass to sprintf
  24. if( !is_array( $args[1] ) ) {
  25. // Colorize the message first so sprintf doesn't bitch at us
  26. $args[0] = Colors::colorize( $args[0] );
  27. return call_user_func_array( 'sprintf', $args );
  28. }
  29. // Here we do named replacement so formatting strings are more understandable
  30. foreach( $args[1] as $key => $value ) {
  31. $msg = str_replace( '{:' . $key . '}', $value, $msg );
  32. }
  33. return Colors::colorize( $msg );
  34. }
  35. /**
  36. * Shortcut for printing to `STDOUT`. The message and parameters are passed
  37. * through `sprintf` before output.
  38. *
  39. * @param string $msg The message to output in `printf` format.
  40. * @param mixed ... Either scalar arguments or a single array argument.
  41. * @return void
  42. * @see \cli\render()
  43. */
  44. public static function out( $msg ) {
  45. $args = func_get_args();
  46. fwrite( static::$out, call_user_func_array( array( '\\cli\\Streams', 'render' ), $args ) );
  47. }
  48. /**
  49. * Pads `$msg` to the width of the shell before passing to `cli\out`.
  50. *
  51. * @param string $msg The message to pad and pass on.
  52. * @param mixed ... Either scalar arguments or a single array argument.
  53. * @return void
  54. * @see cli\out()
  55. */
  56. public static function out_padded( $msg ) {
  57. $args = func_get_args();
  58. $msg = call_user_func_array( array( '\\cli\\Streams', 'render' ), $args );
  59. \cli\Streams::out( str_pad( $msg, \cli\Shell::columns() ) );
  60. }
  61. /**
  62. * Prints a message to `STDOUT` with a newline appended. See `\cli\out` for
  63. * more documentation.
  64. *
  65. * @see cli\out()
  66. */
  67. public static function line( $msg = '' ) {
  68. // func_get_args is empty if no args are passed even with the default above.
  69. $args = array_merge( func_get_args(), array( '' ) );
  70. $args[0] .= "\n";
  71. call_user_func_array( array( '\\cli\\Streams', 'out' ), $args );
  72. }
  73. /**
  74. * Shortcut for printing to `STDERR`. The message and parameters are passed
  75. * through `sprintf` before output.
  76. *
  77. * @param string $msg The message to output in `printf` format. With no string,
  78. * a newline is printed.
  79. * @param mixed ... Either scalar arguments or a single array argument.
  80. * @return void
  81. */
  82. public static function err( $msg = '' ) {
  83. // func_get_args is empty if no args are passed even with the default above.
  84. $args = array_merge( func_get_args(), array( '' ) );
  85. $args[0] .= "\n";
  86. fwrite( static::$err, call_user_func_array( array( '\\cli\\Streams', 'render' ), $args ) );
  87. }
  88. /**
  89. * Takes input from `STDIN` in the given format. If an end of transmission
  90. * character is sent (^D), an exception is thrown.
  91. *
  92. * @param string $format A valid input format. See `fscanf` for documentation.
  93. * If none is given, all input up to the first newline
  94. * is accepted.
  95. * @return string The input with whitespace trimmed.
  96. * @throws \Exception Thrown if ctrl-D (EOT) is sent as input.
  97. */
  98. public static function input( $format = null ) {
  99. if( $format ) {
  100. fscanf( static::$in, $format . "\n", $line );
  101. } else {
  102. $line = fgets( static::$in );
  103. }
  104. if( $line === false ) {
  105. throw new \Exception( 'Caught ^D during input' );
  106. }
  107. return trim( $line );
  108. }
  109. /**
  110. * Displays an input prompt. If no default value is provided the prompt will
  111. * continue displaying until input is received.
  112. *
  113. * @param string $question The question to ask the user.
  114. * @param string $default A default value if the user provides no input.
  115. * @param string $marker A string to append to the question and default value
  116. * on display.
  117. * @return string The users input.
  118. * @see cli\input()
  119. */
  120. public static function prompt( $question, $default = false, $marker = ': ' ) {
  121. if( $default && strpos( $question, '[' ) === false ) {
  122. $question .= ' [' . $default . ']';
  123. }
  124. while( true ) {
  125. \cli\Streams::out( $question . $marker );
  126. $line = \cli\Streams::line();
  127. if( !empty( $line ) )
  128. return $line;
  129. if( $default !== false )
  130. return $default;
  131. }
  132. }
  133. /**
  134. * Presents a user with a multiple choice question, useful for 'yes/no' type
  135. * questions (which this public static function defaults too).
  136. *
  137. * @param string $question The question to ask the user.
  138. * @param string $valid A string of characters allowed as a response. Case
  139. * is ignored.
  140. * @param string $default The default choice. NULL if a default is not allowed.
  141. * @return string The users choice.
  142. * @see cli\prompt()
  143. */
  144. public static function choose( $question, $choice = 'yn', $default = 'n' ) {
  145. if( !is_string( $choice ) ) {
  146. $choice = join( '', $choice );
  147. }
  148. // Make every choice character lowercase except the default
  149. $choice = str_ireplace( $default, strtoupper( $default ), strtolower( $choice ) );
  150. // Seperate each choice with a forward-slash
  151. $choices = trim( join( '/', preg_split( '//', $choice ) ), '/' );
  152. while( true ) {
  153. $line = \cli\Streams::prompt( sprintf( '%s? [%s]', $question, $choices ), $default, '' );
  154. if( stripos( $choice, $line ) !== false ) {
  155. return strtolower( $line );
  156. }
  157. if( !empty( $default ) ) {
  158. return strtolower( $default );
  159. }
  160. }
  161. }
  162. /**
  163. * Displays an array of strings as a menu where a user can enter a number to
  164. * choose an option. The array must be a single dimension with either strings
  165. * or objects with a `__toString()` method.
  166. *
  167. * @param array $items The list of items the user can choose from.
  168. * @param string $default The index of the default item.
  169. * @param string $title The message displayed to the user when prompted.
  170. * @return string The index of the chosen item.
  171. * @see cli\line()
  172. * @see cli\input()
  173. * @see cli\err()
  174. */
  175. public static function menu( $items, $default = false, $title = 'Choose an item' ) {
  176. $map = array_values( $items );
  177. if( $default && strpos( $title, '[' ) === false && isset( $items[$default] ) ) {
  178. $title .= ' [' . $items[$default] . ']';
  179. }
  180. foreach( $map as $idx => $item ) {
  181. \cli\Streams::line( ' %d. %s', $idx + 1, (string)$item );
  182. }
  183. \cli\Streams::line();
  184. while( true ) {
  185. fwrite( static::$out, sprintf( '%s: ', $title ) );
  186. $line = \cli\Streams::input();
  187. if( is_numeric( $line ) ) {
  188. $line--;
  189. if( isset( $map[$line] ) ) {
  190. return array_search( $map[$line], $items );
  191. }
  192. if( $line < 0 || $line >= count( $map ) ) {
  193. \cli\Streams::err( 'Invalid menu selection: out of range' );
  194. }
  195. } else if( isset( $default ) ) {
  196. return $default;
  197. }
  198. }
  199. }
  200. /**
  201. * Sets one of the streams (input, output, or error) to a `stream` type resource.
  202. *
  203. * Valid $whichStream values are:
  204. * - 'in' (default: STDIN)
  205. * - 'out' (default: STDOUT)
  206. * - 'err' (default: STDERR)
  207. *
  208. * Any custom streams will be closed for you on shutdown, so please don't close stream
  209. * resources used with this method.
  210. *
  211. * @param string $whichStream The stream property to update
  212. * @param resource $stream The new stream resource to use
  213. * @return void
  214. * @throws \Exception Thrown if $stream is not a resource of the 'stream' type.
  215. */
  216. public static function setStream( $whichStream, $stream ) {
  217. if( !is_resource( $stream ) || get_resource_type( $stream ) !== 'stream' ) {
  218. throw new \Exception( 'Invalid resource type!' );
  219. }
  220. if( property_exists( __CLASS__, $whichStream ) ) {
  221. static::${$whichStream} = $stream;
  222. }
  223. register_shutdown_function( function() use ($stream) {
  224. fclose( $stream );
  225. } );
  226. }
  227. }