annotate vendor/consolidation/annotated-command/src/Input/StdinHandler.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 129ea1e6d783
children
rev   line source
Chris@17 1 <?php
Chris@17 2
Chris@17 3 namespace Consolidation\AnnotatedCommand\Input;
Chris@17 4
Chris@17 5 use Symfony\Component\Console\Input\StreamableInputInterface;
Chris@17 6 use Symfony\Component\Console\Input\InputInterface;
Chris@17 7
Chris@17 8 /**
Chris@17 9 * StdinHandler is a thin wrapper around php://stdin. It provides
Chris@17 10 * methods for redirecting input from a file, possibly conditionally
Chris@17 11 * under the control of an Input object.
Chris@17 12 *
Chris@17 13 * Example trivial usage (always reads from stdin):
Chris@17 14 *
Chris@17 15 * class Example implements StdinAwareInterface
Chris@17 16 * {
Chris@17 17 * /**
Chris@17 18 * * @command cat
Chris@17 19 * * @param string $file
Chris@17 20 * * @default $file -
Chris@17 21 * * /
Chris@17 22 * public function cat()
Chris@17 23 * {
Chris@17 24 * print($this->stdin()->contents());
Chris@17 25 * }
Chris@17 26 * }
Chris@17 27 *
Chris@17 28 * Command that reads from stdin or file via an option:
Chris@17 29 *
Chris@17 30 * /**
Chris@17 31 * * @command cat
Chris@17 32 * * @param string $file
Chris@17 33 * * @default $file -
Chris@17 34 * * /
Chris@17 35 * public function cat(InputInterface $input)
Chris@17 36 * {
Chris@17 37 * $data = $this->stdin()->select($input, 'file')->contents();
Chris@17 38 * }
Chris@17 39 *
Chris@17 40 * Command that reads from stdin or file via an option:
Chris@17 41 *
Chris@17 42 * /**
Chris@17 43 * * @command cat
Chris@17 44 * * @option string $file
Chris@17 45 * * @default $file -
Chris@17 46 * * /
Chris@17 47 * public function cat(InputInterface $input)
Chris@17 48 * {
Chris@17 49 * $data = $this->stdin()->select($input, 'file')->contents();
Chris@17 50 * }
Chris@17 51 *
Chris@17 52 * It is also possible to inject the selected stream into the input object,
Chris@17 53 * e.g. if you want the contents of the source file to be fed to any Question
Chris@17 54 * helper et. al. that the $input object is used with.
Chris@17 55 *
Chris@17 56 * /**
Chris@17 57 * * @command example
Chris@17 58 * * @option string $file
Chris@17 59 * * @default $file -
Chris@17 60 * * /
Chris@17 61 * public function example(InputInterface $input)
Chris@17 62 * {
Chris@17 63 * $this->stdin()->setStream($input, 'file');
Chris@17 64 * }
Chris@17 65 *
Chris@17 66 *
Chris@17 67 * Inject an alternate source for standard input in tests. Presumes that
Chris@17 68 * the object under test gets a reference to the StdinHandler via dependency
Chris@17 69 * injection from the container.
Chris@17 70 *
Chris@17 71 * $container->get('stdinHandler')->redirect($pathToTestStdinFileFixture);
Chris@17 72 *
Chris@17 73 * You may also inject your stdin file fixture stream into the $input object
Chris@17 74 * as usual, and then use it with 'select()' or 'setStream()' as shown above.
Chris@17 75 *
Chris@17 76 * Finally, this class may also be used in absence of a dependency injection
Chris@17 77 * container by using the static 'selectStream()' method:
Chris@17 78 *
Chris@17 79 * /**
Chris@17 80 * * @command example
Chris@17 81 * * @option string $file
Chris@17 82 * * @default $file -
Chris@17 83 * * /
Chris@17 84 * public function example(InputInterface $input)
Chris@17 85 * {
Chris@17 86 * $data = StdinHandler::selectStream($input, 'file')->contents();
Chris@17 87 * }
Chris@17 88 *
Chris@17 89 * To test a method that uses this technique, simply inject your stdin
Chris@17 90 * fixture into the $input object in your test:
Chris@17 91 *
Chris@17 92 * $input->setStream(fopen($pathToFixture, 'r'));
Chris@17 93 */
Chris@17 94 class StdinHandler
Chris@17 95 {
Chris@17 96 protected $path;
Chris@17 97 protected $stream;
Chris@17 98
Chris@17 99 public static function selectStream(InputInterface $input, $optionOrArg)
Chris@17 100 {
Chris@17 101 $handler = new Self();
Chris@17 102
Chris@17 103 return $handler->setStream($input, $optionOrArg);
Chris@17 104 }
Chris@17 105
Chris@17 106 /**
Chris@17 107 * hasPath returns 'true' if the stdin handler has a path to a file.
Chris@17 108 *
Chris@17 109 * @return bool
Chris@17 110 */
Chris@17 111 public function hasPath()
Chris@17 112 {
Chris@17 113 // Once the stream has been opened, we mask the existence of the path.
Chris@17 114 return !$this->hasStream() && !empty($this->path);
Chris@17 115 }
Chris@17 116
Chris@17 117 /**
Chris@17 118 * hasStream returns 'true' if the stdin handler has opened a stream.
Chris@17 119 *
Chris@17 120 * @return bool
Chris@17 121 */
Chris@17 122 public function hasStream()
Chris@17 123 {
Chris@17 124 return !empty($this->stream);
Chris@17 125 }
Chris@17 126
Chris@17 127 /**
Chris@17 128 * path returns the path to any file that was set as a redirection
Chris@17 129 * source, or `php://stdin` if none have been.
Chris@17 130 *
Chris@17 131 * @return string
Chris@17 132 */
Chris@17 133 public function path()
Chris@17 134 {
Chris@17 135 return $this->path ?: 'php://stdin';
Chris@17 136 }
Chris@17 137
Chris@17 138 /**
Chris@17 139 * close closes the input stream if it was opened.
Chris@17 140 */
Chris@17 141 public function close()
Chris@17 142 {
Chris@17 143 if ($this->hasStream()) {
Chris@17 144 fclose($this->stream);
Chris@17 145 $this->stream = null;
Chris@17 146 }
Chris@17 147 return $this;
Chris@17 148 }
Chris@17 149
Chris@17 150 /**
Chris@17 151 * redirect specifies a path to a file that should serve as the
Chris@17 152 * source to read from. If the input path is '-' or empty,
Chris@17 153 * then output will be taken from php://stdin (or whichever source
Chris@17 154 * was provided via the 'redirect' method).
Chris@17 155 *
Chris@17 156 * @return $this
Chris@17 157 */
Chris@17 158 public function redirect($path)
Chris@17 159 {
Chris@17 160 if ($this->pathProvided($path)) {
Chris@17 161 $this->path = $path;
Chris@17 162 }
Chris@17 163
Chris@17 164 return $this;
Chris@17 165 }
Chris@17 166
Chris@17 167 /**
Chris@17 168 * select chooses the source of the input stream based on whether or
Chris@17 169 * not the user provided the specified option or argument on the commandline.
Chris@17 170 * Stdin is selected if there is no user selection.
Chris@17 171 *
Chris@17 172 * @param InputInterface $input
Chris@17 173 * @param string $optionOrArg
Chris@17 174 * @return $this
Chris@17 175 */
Chris@17 176 public function select(InputInterface $input, $optionOrArg)
Chris@17 177 {
Chris@17 178 $this->redirect($this->getOptionOrArg($input, $optionOrArg));
Chris@17 179 if (!$this->hasPath() && ($input instanceof StreamableInputInterface)) {
Chris@17 180 $this->stream = $input->getStream();
Chris@17 181 }
Chris@17 182
Chris@17 183 return $this;
Chris@17 184 }
Chris@17 185
Chris@17 186 /**
Chris@17 187 * getStream opens and returns the stdin stream (or redirect file).
Chris@17 188 */
Chris@17 189 public function getStream()
Chris@17 190 {
Chris@17 191 if (!$this->hasStream()) {
Chris@17 192 $this->stream = fopen($this->path(), 'r');
Chris@17 193 }
Chris@17 194 return $this->stream;
Chris@17 195 }
Chris@17 196
Chris@17 197 /**
Chris@17 198 * setStream functions like 'select', and also sets up the $input
Chris@17 199 * object to read from the selected input stream e.g. when used
Chris@17 200 * with a question helper.
Chris@17 201 */
Chris@17 202 public function setStream(InputInterface $input, $optionOrArg)
Chris@17 203 {
Chris@17 204 $this->select($input, $optionOrArg);
Chris@17 205 if ($input instanceof StreamableInputInterface) {
Chris@17 206 $stream = $this->getStream();
Chris@17 207 $input->setStream($stream);
Chris@17 208 }
Chris@17 209 return $this;
Chris@17 210 }
Chris@17 211
Chris@17 212 /**
Chris@17 213 * contents reads the entire contents of the standard input stream.
Chris@17 214 *
Chris@17 215 * @return string
Chris@17 216 */
Chris@17 217 public function contents()
Chris@17 218 {
Chris@17 219 // Optimization: use file_get_contents if we have a path to a file
Chris@17 220 // and the stream has not been opened yet.
Chris@17 221 if (!$this->hasStream()) {
Chris@17 222 return file_get_contents($this->path());
Chris@17 223 }
Chris@17 224 $stream = $this->getStream();
Chris@17 225 stream_set_blocking($stream, false); // TODO: We did this in backend invoke. Necessary here?
Chris@17 226 $contents = stream_get_contents($stream);
Chris@17 227 $this->close();
Chris@17 228
Chris@17 229 return $contents;
Chris@17 230 }
Chris@17 231
Chris@17 232 /**
Chris@17 233 * Returns 'true' if a path was specfied, and that path was not '-'.
Chris@17 234 */
Chris@17 235 protected function pathProvided($path)
Chris@17 236 {
Chris@17 237 return !empty($path) && ($path != '-');
Chris@17 238 }
Chris@17 239
Chris@17 240 protected function getOptionOrArg(InputInterface $input, $optionOrArg)
Chris@17 241 {
Chris@17 242 if ($input->hasOption($optionOrArg)) {
Chris@17 243 return $input->getOption($optionOrArg);
Chris@17 244 }
Chris@17 245 return $input->getArgument($optionOrArg);
Chris@17 246 }
Chris@17 247 }