@@ -19,14 +19,14 @@ |
||
19 | 19 | */ |
20 | 20 | interface ConsoleOutputInterface extends OutputInterface |
21 | 21 | { |
22 | - /** |
|
23 | - * Gets the OutputInterface for errors. |
|
24 | - * |
|
25 | - * @return OutputInterface |
|
26 | - */ |
|
27 | - public function getErrorOutput(); |
|
22 | + /** |
|
23 | + * Gets the OutputInterface for errors. |
|
24 | + * |
|
25 | + * @return OutputInterface |
|
26 | + */ |
|
27 | + public function getErrorOutput(); |
|
28 | 28 | |
29 | - public function setErrorOutput(OutputInterface $error); |
|
29 | + public function setErrorOutput(OutputInterface $error); |
|
30 | 30 | |
31 | - public function section(): ConsoleSectionOutput; |
|
31 | + public function section(): ConsoleSectionOutput; |
|
32 | 32 | } |
@@ -29,138 +29,138 @@ |
||
29 | 29 | */ |
30 | 30 | class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface |
31 | 31 | { |
32 | - private $stderr; |
|
33 | - private $consoleSectionOutputs = []; |
|
34 | - |
|
35 | - /** |
|
36 | - * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) |
|
37 | - * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) |
|
38 | - * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) |
|
39 | - */ |
|
40 | - public function __construct(int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null) |
|
41 | - { |
|
42 | - parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter); |
|
43 | - |
|
44 | - if (null === $formatter) { |
|
45 | - // for BC reasons, stdErr has it own Formatter only when user don't inject a specific formatter. |
|
46 | - $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated); |
|
47 | - |
|
48 | - return; |
|
49 | - } |
|
50 | - |
|
51 | - $actualDecorated = $this->isDecorated(); |
|
52 | - $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter()); |
|
53 | - |
|
54 | - if (null === $decorated) { |
|
55 | - $this->setDecorated($actualDecorated && $this->stderr->isDecorated()); |
|
56 | - } |
|
57 | - } |
|
58 | - |
|
59 | - /** |
|
60 | - * Creates a new output section. |
|
61 | - */ |
|
62 | - public function section(): ConsoleSectionOutput |
|
63 | - { |
|
64 | - return new ConsoleSectionOutput($this->getStream(), $this->consoleSectionOutputs, $this->getVerbosity(), $this->isDecorated(), $this->getFormatter()); |
|
65 | - } |
|
66 | - |
|
67 | - /** |
|
68 | - * {@inheritdoc} |
|
69 | - */ |
|
70 | - public function setDecorated(bool $decorated) |
|
71 | - { |
|
72 | - parent::setDecorated($decorated); |
|
73 | - $this->stderr->setDecorated($decorated); |
|
74 | - } |
|
75 | - |
|
76 | - /** |
|
77 | - * {@inheritdoc} |
|
78 | - */ |
|
79 | - public function setFormatter(OutputFormatterInterface $formatter) |
|
80 | - { |
|
81 | - parent::setFormatter($formatter); |
|
82 | - $this->stderr->setFormatter($formatter); |
|
83 | - } |
|
84 | - |
|
85 | - /** |
|
86 | - * {@inheritdoc} |
|
87 | - */ |
|
88 | - public function setVerbosity(int $level) |
|
89 | - { |
|
90 | - parent::setVerbosity($level); |
|
91 | - $this->stderr->setVerbosity($level); |
|
92 | - } |
|
93 | - |
|
94 | - /** |
|
95 | - * {@inheritdoc} |
|
96 | - */ |
|
97 | - public function getErrorOutput() |
|
98 | - { |
|
99 | - return $this->stderr; |
|
100 | - } |
|
101 | - |
|
102 | - /** |
|
103 | - * {@inheritdoc} |
|
104 | - */ |
|
105 | - public function setErrorOutput(OutputInterface $error) |
|
106 | - { |
|
107 | - $this->stderr = $error; |
|
108 | - } |
|
109 | - |
|
110 | - /** |
|
111 | - * Returns true if current environment supports writing console output to |
|
112 | - * STDOUT. |
|
113 | - * |
|
114 | - * @return bool |
|
115 | - */ |
|
116 | - protected function hasStdoutSupport() |
|
117 | - { |
|
118 | - return false === $this->isRunningOS400(); |
|
119 | - } |
|
120 | - |
|
121 | - /** |
|
122 | - * Returns true if current environment supports writing console output to |
|
123 | - * STDERR. |
|
124 | - * |
|
125 | - * @return bool |
|
126 | - */ |
|
127 | - protected function hasStderrSupport() |
|
128 | - { |
|
129 | - return false === $this->isRunningOS400(); |
|
130 | - } |
|
131 | - |
|
132 | - /** |
|
133 | - * Checks if current executing environment is IBM iSeries (OS400), which |
|
134 | - * doesn't properly convert character-encodings between ASCII to EBCDIC. |
|
135 | - */ |
|
136 | - private function isRunningOS400(): bool |
|
137 | - { |
|
138 | - $checks = [ |
|
139 | - \function_exists('php_uname') ? php_uname('s') : '', |
|
140 | - getenv('OSTYPE'), |
|
141 | - \PHP_OS, |
|
142 | - ]; |
|
143 | - |
|
144 | - return false !== stripos(implode(';', $checks), 'OS400'); |
|
145 | - } |
|
146 | - |
|
147 | - /** |
|
148 | - * @return resource |
|
149 | - */ |
|
150 | - private function openOutputStream() |
|
151 | - { |
|
152 | - if (!$this->hasStdoutSupport()) { |
|
153 | - return fopen('php://output', 'w'); |
|
154 | - } |
|
155 | - |
|
156 | - return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w'); |
|
157 | - } |
|
158 | - |
|
159 | - /** |
|
160 | - * @return resource |
|
161 | - */ |
|
162 | - private function openErrorStream() |
|
163 | - { |
|
164 | - return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w'); |
|
165 | - } |
|
32 | + private $stderr; |
|
33 | + private $consoleSectionOutputs = []; |
|
34 | + |
|
35 | + /** |
|
36 | + * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) |
|
37 | + * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) |
|
38 | + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) |
|
39 | + */ |
|
40 | + public function __construct(int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null) |
|
41 | + { |
|
42 | + parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter); |
|
43 | + |
|
44 | + if (null === $formatter) { |
|
45 | + // for BC reasons, stdErr has it own Formatter only when user don't inject a specific formatter. |
|
46 | + $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated); |
|
47 | + |
|
48 | + return; |
|
49 | + } |
|
50 | + |
|
51 | + $actualDecorated = $this->isDecorated(); |
|
52 | + $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter()); |
|
53 | + |
|
54 | + if (null === $decorated) { |
|
55 | + $this->setDecorated($actualDecorated && $this->stderr->isDecorated()); |
|
56 | + } |
|
57 | + } |
|
58 | + |
|
59 | + /** |
|
60 | + * Creates a new output section. |
|
61 | + */ |
|
62 | + public function section(): ConsoleSectionOutput |
|
63 | + { |
|
64 | + return new ConsoleSectionOutput($this->getStream(), $this->consoleSectionOutputs, $this->getVerbosity(), $this->isDecorated(), $this->getFormatter()); |
|
65 | + } |
|
66 | + |
|
67 | + /** |
|
68 | + * {@inheritdoc} |
|
69 | + */ |
|
70 | + public function setDecorated(bool $decorated) |
|
71 | + { |
|
72 | + parent::setDecorated($decorated); |
|
73 | + $this->stderr->setDecorated($decorated); |
|
74 | + } |
|
75 | + |
|
76 | + /** |
|
77 | + * {@inheritdoc} |
|
78 | + */ |
|
79 | + public function setFormatter(OutputFormatterInterface $formatter) |
|
80 | + { |
|
81 | + parent::setFormatter($formatter); |
|
82 | + $this->stderr->setFormatter($formatter); |
|
83 | + } |
|
84 | + |
|
85 | + /** |
|
86 | + * {@inheritdoc} |
|
87 | + */ |
|
88 | + public function setVerbosity(int $level) |
|
89 | + { |
|
90 | + parent::setVerbosity($level); |
|
91 | + $this->stderr->setVerbosity($level); |
|
92 | + } |
|
93 | + |
|
94 | + /** |
|
95 | + * {@inheritdoc} |
|
96 | + */ |
|
97 | + public function getErrorOutput() |
|
98 | + { |
|
99 | + return $this->stderr; |
|
100 | + } |
|
101 | + |
|
102 | + /** |
|
103 | + * {@inheritdoc} |
|
104 | + */ |
|
105 | + public function setErrorOutput(OutputInterface $error) |
|
106 | + { |
|
107 | + $this->stderr = $error; |
|
108 | + } |
|
109 | + |
|
110 | + /** |
|
111 | + * Returns true if current environment supports writing console output to |
|
112 | + * STDOUT. |
|
113 | + * |
|
114 | + * @return bool |
|
115 | + */ |
|
116 | + protected function hasStdoutSupport() |
|
117 | + { |
|
118 | + return false === $this->isRunningOS400(); |
|
119 | + } |
|
120 | + |
|
121 | + /** |
|
122 | + * Returns true if current environment supports writing console output to |
|
123 | + * STDERR. |
|
124 | + * |
|
125 | + * @return bool |
|
126 | + */ |
|
127 | + protected function hasStderrSupport() |
|
128 | + { |
|
129 | + return false === $this->isRunningOS400(); |
|
130 | + } |
|
131 | + |
|
132 | + /** |
|
133 | + * Checks if current executing environment is IBM iSeries (OS400), which |
|
134 | + * doesn't properly convert character-encodings between ASCII to EBCDIC. |
|
135 | + */ |
|
136 | + private function isRunningOS400(): bool |
|
137 | + { |
|
138 | + $checks = [ |
|
139 | + \function_exists('php_uname') ? php_uname('s') : '', |
|
140 | + getenv('OSTYPE'), |
|
141 | + \PHP_OS, |
|
142 | + ]; |
|
143 | + |
|
144 | + return false !== stripos(implode(';', $checks), 'OS400'); |
|
145 | + } |
|
146 | + |
|
147 | + /** |
|
148 | + * @return resource |
|
149 | + */ |
|
150 | + private function openOutputStream() |
|
151 | + { |
|
152 | + if (!$this->hasStdoutSupport()) { |
|
153 | + return fopen('php://output', 'w'); |
|
154 | + } |
|
155 | + |
|
156 | + return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w'); |
|
157 | + } |
|
158 | + |
|
159 | + /** |
|
160 | + * @return resource |
|
161 | + */ |
|
162 | + private function openErrorStream() |
|
163 | + { |
|
164 | + return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w'); |
|
165 | + } |
|
166 | 166 | } |
@@ -20,91 +20,91 @@ |
||
20 | 20 | */ |
21 | 21 | interface OutputInterface |
22 | 22 | { |
23 | - public const VERBOSITY_QUIET = 16; |
|
24 | - public const VERBOSITY_NORMAL = 32; |
|
25 | - public const VERBOSITY_VERBOSE = 64; |
|
26 | - public const VERBOSITY_VERY_VERBOSE = 128; |
|
27 | - public const VERBOSITY_DEBUG = 256; |
|
28 | - |
|
29 | - public const OUTPUT_NORMAL = 1; |
|
30 | - public const OUTPUT_RAW = 2; |
|
31 | - public const OUTPUT_PLAIN = 4; |
|
32 | - |
|
33 | - /** |
|
34 | - * Writes a message to the output. |
|
35 | - * |
|
36 | - * @param string|iterable $messages The message as an iterable of strings or a single string |
|
37 | - * @param bool $newline Whether to add a newline |
|
38 | - * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL |
|
39 | - */ |
|
40 | - public function write($messages, bool $newline = false, int $options = 0); |
|
41 | - |
|
42 | - /** |
|
43 | - * Writes a message to the output and adds a newline at the end. |
|
44 | - * |
|
45 | - * @param string|iterable $messages The message as an iterable of strings or a single string |
|
46 | - * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL |
|
47 | - */ |
|
48 | - public function writeln($messages, int $options = 0); |
|
49 | - |
|
50 | - /** |
|
51 | - * Sets the verbosity of the output. |
|
52 | - */ |
|
53 | - public function setVerbosity(int $level); |
|
54 | - |
|
55 | - /** |
|
56 | - * Gets the current verbosity of the output. |
|
57 | - * |
|
58 | - * @return int The current level of verbosity (one of the VERBOSITY constants) |
|
59 | - */ |
|
60 | - public function getVerbosity(); |
|
61 | - |
|
62 | - /** |
|
63 | - * Returns whether verbosity is quiet (-q). |
|
64 | - * |
|
65 | - * @return bool true if verbosity is set to VERBOSITY_QUIET, false otherwise |
|
66 | - */ |
|
67 | - public function isQuiet(); |
|
68 | - |
|
69 | - /** |
|
70 | - * Returns whether verbosity is verbose (-v). |
|
71 | - * |
|
72 | - * @return bool true if verbosity is set to VERBOSITY_VERBOSE, false otherwise |
|
73 | - */ |
|
74 | - public function isVerbose(); |
|
75 | - |
|
76 | - /** |
|
77 | - * Returns whether verbosity is very verbose (-vv). |
|
78 | - * |
|
79 | - * @return bool true if verbosity is set to VERBOSITY_VERY_VERBOSE, false otherwise |
|
80 | - */ |
|
81 | - public function isVeryVerbose(); |
|
82 | - |
|
83 | - /** |
|
84 | - * Returns whether verbosity is debug (-vvv). |
|
85 | - * |
|
86 | - * @return bool true if verbosity is set to VERBOSITY_DEBUG, false otherwise |
|
87 | - */ |
|
88 | - public function isDebug(); |
|
89 | - |
|
90 | - /** |
|
91 | - * Sets the decorated flag. |
|
92 | - */ |
|
93 | - public function setDecorated(bool $decorated); |
|
94 | - |
|
95 | - /** |
|
96 | - * Gets the decorated flag. |
|
97 | - * |
|
98 | - * @return bool true if the output will decorate messages, false otherwise |
|
99 | - */ |
|
100 | - public function isDecorated(); |
|
101 | - |
|
102 | - public function setFormatter(OutputFormatterInterface $formatter); |
|
103 | - |
|
104 | - /** |
|
105 | - * Returns current output formatter instance. |
|
106 | - * |
|
107 | - * @return OutputFormatterInterface |
|
108 | - */ |
|
109 | - public function getFormatter(); |
|
23 | + public const VERBOSITY_QUIET = 16; |
|
24 | + public const VERBOSITY_NORMAL = 32; |
|
25 | + public const VERBOSITY_VERBOSE = 64; |
|
26 | + public const VERBOSITY_VERY_VERBOSE = 128; |
|
27 | + public const VERBOSITY_DEBUG = 256; |
|
28 | + |
|
29 | + public const OUTPUT_NORMAL = 1; |
|
30 | + public const OUTPUT_RAW = 2; |
|
31 | + public const OUTPUT_PLAIN = 4; |
|
32 | + |
|
33 | + /** |
|
34 | + * Writes a message to the output. |
|
35 | + * |
|
36 | + * @param string|iterable $messages The message as an iterable of strings or a single string |
|
37 | + * @param bool $newline Whether to add a newline |
|
38 | + * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL |
|
39 | + */ |
|
40 | + public function write($messages, bool $newline = false, int $options = 0); |
|
41 | + |
|
42 | + /** |
|
43 | + * Writes a message to the output and adds a newline at the end. |
|
44 | + * |
|
45 | + * @param string|iterable $messages The message as an iterable of strings or a single string |
|
46 | + * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL |
|
47 | + */ |
|
48 | + public function writeln($messages, int $options = 0); |
|
49 | + |
|
50 | + /** |
|
51 | + * Sets the verbosity of the output. |
|
52 | + */ |
|
53 | + public function setVerbosity(int $level); |
|
54 | + |
|
55 | + /** |
|
56 | + * Gets the current verbosity of the output. |
|
57 | + * |
|
58 | + * @return int The current level of verbosity (one of the VERBOSITY constants) |
|
59 | + */ |
|
60 | + public function getVerbosity(); |
|
61 | + |
|
62 | + /** |
|
63 | + * Returns whether verbosity is quiet (-q). |
|
64 | + * |
|
65 | + * @return bool true if verbosity is set to VERBOSITY_QUIET, false otherwise |
|
66 | + */ |
|
67 | + public function isQuiet(); |
|
68 | + |
|
69 | + /** |
|
70 | + * Returns whether verbosity is verbose (-v). |
|
71 | + * |
|
72 | + * @return bool true if verbosity is set to VERBOSITY_VERBOSE, false otherwise |
|
73 | + */ |
|
74 | + public function isVerbose(); |
|
75 | + |
|
76 | + /** |
|
77 | + * Returns whether verbosity is very verbose (-vv). |
|
78 | + * |
|
79 | + * @return bool true if verbosity is set to VERBOSITY_VERY_VERBOSE, false otherwise |
|
80 | + */ |
|
81 | + public function isVeryVerbose(); |
|
82 | + |
|
83 | + /** |
|
84 | + * Returns whether verbosity is debug (-vvv). |
|
85 | + * |
|
86 | + * @return bool true if verbosity is set to VERBOSITY_DEBUG, false otherwise |
|
87 | + */ |
|
88 | + public function isDebug(); |
|
89 | + |
|
90 | + /** |
|
91 | + * Sets the decorated flag. |
|
92 | + */ |
|
93 | + public function setDecorated(bool $decorated); |
|
94 | + |
|
95 | + /** |
|
96 | + * Gets the decorated flag. |
|
97 | + * |
|
98 | + * @return bool true if the output will decorate messages, false otherwise |
|
99 | + */ |
|
100 | + public function isDecorated(); |
|
101 | + |
|
102 | + public function setFormatter(OutputFormatterInterface $formatter); |
|
103 | + |
|
104 | + /** |
|
105 | + * Returns current output formatter instance. |
|
106 | + * |
|
107 | + * @return OutputFormatterInterface |
|
108 | + */ |
|
109 | + public function getFormatter(); |
|
110 | 110 | } |
@@ -29,87 +29,87 @@ |
||
29 | 29 | */ |
30 | 30 | class StreamOutput extends Output |
31 | 31 | { |
32 | - private $stream; |
|
33 | - |
|
34 | - /** |
|
35 | - * @param resource $stream A stream resource |
|
36 | - * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) |
|
37 | - * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) |
|
38 | - * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) |
|
39 | - * |
|
40 | - * @throws InvalidArgumentException When first argument is not a real stream |
|
41 | - */ |
|
42 | - public function __construct($stream, int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null) |
|
43 | - { |
|
44 | - if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) { |
|
45 | - throw new InvalidArgumentException('The StreamOutput class needs a stream as its first argument.'); |
|
46 | - } |
|
47 | - |
|
48 | - $this->stream = $stream; |
|
49 | - |
|
50 | - if (null === $decorated) { |
|
51 | - $decorated = $this->hasColorSupport(); |
|
52 | - } |
|
53 | - |
|
54 | - parent::__construct($verbosity, $decorated, $formatter); |
|
55 | - } |
|
56 | - |
|
57 | - /** |
|
58 | - * Gets the stream attached to this StreamOutput instance. |
|
59 | - * |
|
60 | - * @return resource A stream resource |
|
61 | - */ |
|
62 | - public function getStream() |
|
63 | - { |
|
64 | - return $this->stream; |
|
65 | - } |
|
66 | - |
|
67 | - /** |
|
68 | - * {@inheritdoc} |
|
69 | - */ |
|
70 | - protected function doWrite(string $message, bool $newline) |
|
71 | - { |
|
72 | - if ($newline) { |
|
73 | - $message .= \PHP_EOL; |
|
74 | - } |
|
75 | - |
|
76 | - @fwrite($this->stream, $message); |
|
77 | - |
|
78 | - fflush($this->stream); |
|
79 | - } |
|
80 | - |
|
81 | - /** |
|
82 | - * Returns true if the stream supports colorization. |
|
83 | - * |
|
84 | - * Colorization is disabled if not supported by the stream: |
|
85 | - * |
|
86 | - * This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo |
|
87 | - * terminals via named pipes, so we can only check the environment. |
|
88 | - * |
|
89 | - * Reference: Composer\XdebugHandler\Process::supportsColor |
|
90 | - * https://github.com/composer/xdebug-handler |
|
91 | - * |
|
92 | - * @return bool true if the stream supports colorization, false otherwise |
|
93 | - */ |
|
94 | - protected function hasColorSupport() |
|
95 | - { |
|
96 | - // Follow https://no-color.org/ |
|
97 | - if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) { |
|
98 | - return false; |
|
99 | - } |
|
100 | - |
|
101 | - if ('Hyper' === getenv('TERM_PROGRAM')) { |
|
102 | - return true; |
|
103 | - } |
|
104 | - |
|
105 | - if (\DIRECTORY_SEPARATOR === '\\') { |
|
106 | - return (\function_exists('sapi_windows_vt100_support') |
|
107 | - && @sapi_windows_vt100_support($this->stream)) |
|
108 | - || false !== getenv('ANSICON') |
|
109 | - || 'ON' === getenv('ConEmuANSI') |
|
110 | - || 'xterm' === getenv('TERM'); |
|
111 | - } |
|
112 | - |
|
113 | - return stream_isatty($this->stream); |
|
114 | - } |
|
32 | + private $stream; |
|
33 | + |
|
34 | + /** |
|
35 | + * @param resource $stream A stream resource |
|
36 | + * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) |
|
37 | + * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) |
|
38 | + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) |
|
39 | + * |
|
40 | + * @throws InvalidArgumentException When first argument is not a real stream |
|
41 | + */ |
|
42 | + public function __construct($stream, int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null) |
|
43 | + { |
|
44 | + if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) { |
|
45 | + throw new InvalidArgumentException('The StreamOutput class needs a stream as its first argument.'); |
|
46 | + } |
|
47 | + |
|
48 | + $this->stream = $stream; |
|
49 | + |
|
50 | + if (null === $decorated) { |
|
51 | + $decorated = $this->hasColorSupport(); |
|
52 | + } |
|
53 | + |
|
54 | + parent::__construct($verbosity, $decorated, $formatter); |
|
55 | + } |
|
56 | + |
|
57 | + /** |
|
58 | + * Gets the stream attached to this StreamOutput instance. |
|
59 | + * |
|
60 | + * @return resource A stream resource |
|
61 | + */ |
|
62 | + public function getStream() |
|
63 | + { |
|
64 | + return $this->stream; |
|
65 | + } |
|
66 | + |
|
67 | + /** |
|
68 | + * {@inheritdoc} |
|
69 | + */ |
|
70 | + protected function doWrite(string $message, bool $newline) |
|
71 | + { |
|
72 | + if ($newline) { |
|
73 | + $message .= \PHP_EOL; |
|
74 | + } |
|
75 | + |
|
76 | + @fwrite($this->stream, $message); |
|
77 | + |
|
78 | + fflush($this->stream); |
|
79 | + } |
|
80 | + |
|
81 | + /** |
|
82 | + * Returns true if the stream supports colorization. |
|
83 | + * |
|
84 | + * Colorization is disabled if not supported by the stream: |
|
85 | + * |
|
86 | + * This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo |
|
87 | + * terminals via named pipes, so we can only check the environment. |
|
88 | + * |
|
89 | + * Reference: Composer\XdebugHandler\Process::supportsColor |
|
90 | + * https://github.com/composer/xdebug-handler |
|
91 | + * |
|
92 | + * @return bool true if the stream supports colorization, false otherwise |
|
93 | + */ |
|
94 | + protected function hasColorSupport() |
|
95 | + { |
|
96 | + // Follow https://no-color.org/ |
|
97 | + if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) { |
|
98 | + return false; |
|
99 | + } |
|
100 | + |
|
101 | + if ('Hyper' === getenv('TERM_PROGRAM')) { |
|
102 | + return true; |
|
103 | + } |
|
104 | + |
|
105 | + if (\DIRECTORY_SEPARATOR === '\\') { |
|
106 | + return (\function_exists('sapi_windows_vt100_support') |
|
107 | + && @sapi_windows_vt100_support($this->stream)) |
|
108 | + || false !== getenv('ANSICON') |
|
109 | + || 'ON' === getenv('ConEmuANSI') |
|
110 | + || 'xterm' === getenv('TERM'); |
|
111 | + } |
|
112 | + |
|
113 | + return stream_isatty($this->stream); |
|
114 | + } |
|
115 | 115 | } |
@@ -21,42 +21,42 @@ |
||
21 | 21 | */ |
22 | 22 | class TrimmedBufferOutput extends Output |
23 | 23 | { |
24 | - private $maxLength; |
|
25 | - private $buffer = ''; |
|
26 | - |
|
27 | - public function __construct(int $maxLength, ?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, OutputFormatterInterface $formatter = null) { |
|
28 | - if ($maxLength <= 0) { |
|
29 | - throw new InvalidArgumentException(sprintf('"%s()" expects a strictly positive maxLength. Got %d.', __METHOD__, $maxLength)); |
|
30 | - } |
|
31 | - |
|
32 | - parent::__construct($verbosity, $decorated, $formatter); |
|
33 | - $this->maxLength = $maxLength; |
|
34 | - } |
|
35 | - |
|
36 | - /** |
|
37 | - * Empties buffer and returns its content. |
|
38 | - * |
|
39 | - * @return string |
|
40 | - */ |
|
41 | - public function fetch() |
|
42 | - { |
|
43 | - $content = $this->buffer; |
|
44 | - $this->buffer = ''; |
|
45 | - |
|
46 | - return $content; |
|
47 | - } |
|
48 | - |
|
49 | - /** |
|
50 | - * {@inheritdoc} |
|
51 | - */ |
|
52 | - protected function doWrite(string $message, bool $newline) |
|
53 | - { |
|
54 | - $this->buffer .= $message; |
|
55 | - |
|
56 | - if ($newline) { |
|
57 | - $this->buffer .= \PHP_EOL; |
|
58 | - } |
|
59 | - |
|
60 | - $this->buffer = substr($this->buffer, 0 - $this->maxLength); |
|
61 | - } |
|
24 | + private $maxLength; |
|
25 | + private $buffer = ''; |
|
26 | + |
|
27 | + public function __construct(int $maxLength, ?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, OutputFormatterInterface $formatter = null) { |
|
28 | + if ($maxLength <= 0) { |
|
29 | + throw new InvalidArgumentException(sprintf('"%s()" expects a strictly positive maxLength. Got %d.', __METHOD__, $maxLength)); |
|
30 | + } |
|
31 | + |
|
32 | + parent::__construct($verbosity, $decorated, $formatter); |
|
33 | + $this->maxLength = $maxLength; |
|
34 | + } |
|
35 | + |
|
36 | + /** |
|
37 | + * Empties buffer and returns its content. |
|
38 | + * |
|
39 | + * @return string |
|
40 | + */ |
|
41 | + public function fetch() |
|
42 | + { |
|
43 | + $content = $this->buffer; |
|
44 | + $this->buffer = ''; |
|
45 | + |
|
46 | + return $content; |
|
47 | + } |
|
48 | + |
|
49 | + /** |
|
50 | + * {@inheritdoc} |
|
51 | + */ |
|
52 | + protected function doWrite(string $message, bool $newline) |
|
53 | + { |
|
54 | + $this->buffer .= $message; |
|
55 | + |
|
56 | + if ($newline) { |
|
57 | + $this->buffer .= \PHP_EOL; |
|
58 | + } |
|
59 | + |
|
60 | + $this->buffer = substr($this->buffer, 0 - $this->maxLength); |
|
61 | + } |
|
62 | 62 | } |
@@ -21,123 +21,123 @@ |
||
21 | 21 | */ |
22 | 22 | class ConsoleSectionOutput extends StreamOutput |
23 | 23 | { |
24 | - private $content = []; |
|
25 | - private $lines = 0; |
|
26 | - private $sections; |
|
27 | - private $terminal; |
|
28 | - |
|
29 | - /** |
|
30 | - * @param resource $stream |
|
31 | - * @param ConsoleSectionOutput[] $sections |
|
32 | - */ |
|
33 | - public function __construct($stream, array &$sections, int $verbosity, bool $decorated, OutputFormatterInterface $formatter) |
|
34 | - { |
|
35 | - parent::__construct($stream, $verbosity, $decorated, $formatter); |
|
36 | - array_unshift($sections, $this); |
|
37 | - $this->sections = &$sections; |
|
38 | - $this->terminal = new Terminal(); |
|
39 | - } |
|
40 | - |
|
41 | - /** |
|
42 | - * Clears previous output for this section. |
|
43 | - * |
|
44 | - * @param int $lines Number of lines to clear. If null, then the entire output of this section is cleared |
|
45 | - */ |
|
46 | - public function clear(int $lines = null) |
|
47 | - { |
|
48 | - if (empty($this->content) || !$this->isDecorated()) { |
|
49 | - return; |
|
50 | - } |
|
51 | - |
|
52 | - if ($lines) { |
|
53 | - array_splice($this->content, -($lines * 2)); // Multiply lines by 2 to cater for each new line added between content |
|
54 | - } else { |
|
55 | - $lines = $this->lines; |
|
56 | - $this->content = []; |
|
57 | - } |
|
58 | - |
|
59 | - $this->lines -= $lines; |
|
60 | - |
|
61 | - parent::doWrite($this->popStreamContentUntilCurrentSection($lines), false); |
|
62 | - } |
|
63 | - |
|
64 | - /** |
|
65 | - * Overwrites the previous output with a new message. |
|
66 | - * |
|
67 | - * @param array|string $message |
|
68 | - */ |
|
69 | - public function overwrite($message) |
|
70 | - { |
|
71 | - $this->clear(); |
|
72 | - $this->writeln($message); |
|
73 | - } |
|
74 | - |
|
75 | - public function getContent(): string |
|
76 | - { |
|
77 | - return implode('', $this->content); |
|
78 | - } |
|
79 | - |
|
80 | - /** |
|
81 | - * @internal |
|
82 | - */ |
|
83 | - public function addContent(string $input) |
|
84 | - { |
|
85 | - foreach (explode(\PHP_EOL, $input) as $lineContent) { |
|
86 | - $this->lines += ceil($this->getDisplayLength($lineContent) / $this->terminal->getWidth()) ?: 1; |
|
87 | - $this->content[] = $lineContent; |
|
88 | - $this->content[] = \PHP_EOL; |
|
89 | - } |
|
90 | - } |
|
91 | - |
|
92 | - /** |
|
93 | - * {@inheritdoc} |
|
94 | - */ |
|
95 | - protected function doWrite(string $message, bool $newline) |
|
96 | - { |
|
97 | - if (!$this->isDecorated()) { |
|
98 | - parent::doWrite($message, $newline); |
|
99 | - |
|
100 | - return; |
|
101 | - } |
|
102 | - |
|
103 | - $erasedContent = $this->popStreamContentUntilCurrentSection(); |
|
104 | - |
|
105 | - $this->addContent($message); |
|
106 | - |
|
107 | - parent::doWrite($message, true); |
|
108 | - parent::doWrite($erasedContent, false); |
|
109 | - } |
|
110 | - |
|
111 | - /** |
|
112 | - * At initial stage, cursor is at the end of stream output. This method makes cursor crawl upwards until it hits |
|
113 | - * current section. Then it erases content it crawled through. Optionally, it erases part of current section too. |
|
114 | - */ |
|
115 | - private function popStreamContentUntilCurrentSection(int $numberOfLinesToClearFromCurrentSection = 0): string |
|
116 | - { |
|
117 | - $numberOfLinesToClear = $numberOfLinesToClearFromCurrentSection; |
|
118 | - $erasedContent = []; |
|
119 | - |
|
120 | - foreach ($this->sections as $section) { |
|
121 | - if ($section === $this) { |
|
122 | - break; |
|
123 | - } |
|
124 | - |
|
125 | - $numberOfLinesToClear += $section->lines; |
|
126 | - $erasedContent[] = $section->getContent(); |
|
127 | - } |
|
128 | - |
|
129 | - if ($numberOfLinesToClear > 0) { |
|
130 | - // move cursor up n lines |
|
131 | - parent::doWrite(sprintf("\x1b[%dA", $numberOfLinesToClear), false); |
|
132 | - // erase to end of screen |
|
133 | - parent::doWrite("\x1b[0J", false); |
|
134 | - } |
|
135 | - |
|
136 | - return implode('', array_reverse($erasedContent)); |
|
137 | - } |
|
138 | - |
|
139 | - private function getDisplayLength(string $text): int |
|
140 | - { |
|
141 | - return Helper::width(Helper::removeDecoration($this->getFormatter(), str_replace("\t", ' ', $text))); |
|
142 | - } |
|
24 | + private $content = []; |
|
25 | + private $lines = 0; |
|
26 | + private $sections; |
|
27 | + private $terminal; |
|
28 | + |
|
29 | + /** |
|
30 | + * @param resource $stream |
|
31 | + * @param ConsoleSectionOutput[] $sections |
|
32 | + */ |
|
33 | + public function __construct($stream, array &$sections, int $verbosity, bool $decorated, OutputFormatterInterface $formatter) |
|
34 | + { |
|
35 | + parent::__construct($stream, $verbosity, $decorated, $formatter); |
|
36 | + array_unshift($sections, $this); |
|
37 | + $this->sections = &$sections; |
|
38 | + $this->terminal = new Terminal(); |
|
39 | + } |
|
40 | + |
|
41 | + /** |
|
42 | + * Clears previous output for this section. |
|
43 | + * |
|
44 | + * @param int $lines Number of lines to clear. If null, then the entire output of this section is cleared |
|
45 | + */ |
|
46 | + public function clear(int $lines = null) |
|
47 | + { |
|
48 | + if (empty($this->content) || !$this->isDecorated()) { |
|
49 | + return; |
|
50 | + } |
|
51 | + |
|
52 | + if ($lines) { |
|
53 | + array_splice($this->content, -($lines * 2)); // Multiply lines by 2 to cater for each new line added between content |
|
54 | + } else { |
|
55 | + $lines = $this->lines; |
|
56 | + $this->content = []; |
|
57 | + } |
|
58 | + |
|
59 | + $this->lines -= $lines; |
|
60 | + |
|
61 | + parent::doWrite($this->popStreamContentUntilCurrentSection($lines), false); |
|
62 | + } |
|
63 | + |
|
64 | + /** |
|
65 | + * Overwrites the previous output with a new message. |
|
66 | + * |
|
67 | + * @param array|string $message |
|
68 | + */ |
|
69 | + public function overwrite($message) |
|
70 | + { |
|
71 | + $this->clear(); |
|
72 | + $this->writeln($message); |
|
73 | + } |
|
74 | + |
|
75 | + public function getContent(): string |
|
76 | + { |
|
77 | + return implode('', $this->content); |
|
78 | + } |
|
79 | + |
|
80 | + /** |
|
81 | + * @internal |
|
82 | + */ |
|
83 | + public function addContent(string $input) |
|
84 | + { |
|
85 | + foreach (explode(\PHP_EOL, $input) as $lineContent) { |
|
86 | + $this->lines += ceil($this->getDisplayLength($lineContent) / $this->terminal->getWidth()) ?: 1; |
|
87 | + $this->content[] = $lineContent; |
|
88 | + $this->content[] = \PHP_EOL; |
|
89 | + } |
|
90 | + } |
|
91 | + |
|
92 | + /** |
|
93 | + * {@inheritdoc} |
|
94 | + */ |
|
95 | + protected function doWrite(string $message, bool $newline) |
|
96 | + { |
|
97 | + if (!$this->isDecorated()) { |
|
98 | + parent::doWrite($message, $newline); |
|
99 | + |
|
100 | + return; |
|
101 | + } |
|
102 | + |
|
103 | + $erasedContent = $this->popStreamContentUntilCurrentSection(); |
|
104 | + |
|
105 | + $this->addContent($message); |
|
106 | + |
|
107 | + parent::doWrite($message, true); |
|
108 | + parent::doWrite($erasedContent, false); |
|
109 | + } |
|
110 | + |
|
111 | + /** |
|
112 | + * At initial stage, cursor is at the end of stream output. This method makes cursor crawl upwards until it hits |
|
113 | + * current section. Then it erases content it crawled through. Optionally, it erases part of current section too. |
|
114 | + */ |
|
115 | + private function popStreamContentUntilCurrentSection(int $numberOfLinesToClearFromCurrentSection = 0): string |
|
116 | + { |
|
117 | + $numberOfLinesToClear = $numberOfLinesToClearFromCurrentSection; |
|
118 | + $erasedContent = []; |
|
119 | + |
|
120 | + foreach ($this->sections as $section) { |
|
121 | + if ($section === $this) { |
|
122 | + break; |
|
123 | + } |
|
124 | + |
|
125 | + $numberOfLinesToClear += $section->lines; |
|
126 | + $erasedContent[] = $section->getContent(); |
|
127 | + } |
|
128 | + |
|
129 | + if ($numberOfLinesToClear > 0) { |
|
130 | + // move cursor up n lines |
|
131 | + parent::doWrite(sprintf("\x1b[%dA", $numberOfLinesToClear), false); |
|
132 | + // erase to end of screen |
|
133 | + parent::doWrite("\x1b[0J", false); |
|
134 | + } |
|
135 | + |
|
136 | + return implode('', array_reverse($erasedContent)); |
|
137 | + } |
|
138 | + |
|
139 | + private function getDisplayLength(string $text): int |
|
140 | + { |
|
141 | + return Helper::width(Helper::removeDecoration($this->getFormatter(), str_replace("\t", ' ', $text))); |
|
142 | + } |
|
143 | 143 | } |
@@ -24,105 +24,105 @@ |
||
24 | 24 | */ |
25 | 25 | class NullOutput implements OutputInterface |
26 | 26 | { |
27 | - private $formatter; |
|
28 | - |
|
29 | - /** |
|
30 | - * {@inheritdoc} |
|
31 | - */ |
|
32 | - public function setFormatter(OutputFormatterInterface $formatter) |
|
33 | - { |
|
34 | - // do nothing |
|
35 | - } |
|
36 | - |
|
37 | - /** |
|
38 | - * {@inheritdoc} |
|
39 | - */ |
|
40 | - public function getFormatter() |
|
41 | - { |
|
42 | - if ($this->formatter) { |
|
43 | - return $this->formatter; |
|
44 | - } |
|
45 | - // to comply with the interface we must return a OutputFormatterInterface |
|
46 | - return $this->formatter = new NullOutputFormatter(); |
|
47 | - } |
|
48 | - |
|
49 | - /** |
|
50 | - * {@inheritdoc} |
|
51 | - */ |
|
52 | - public function setDecorated(bool $decorated) |
|
53 | - { |
|
54 | - // do nothing |
|
55 | - } |
|
56 | - |
|
57 | - /** |
|
58 | - * {@inheritdoc} |
|
59 | - */ |
|
60 | - public function isDecorated() |
|
61 | - { |
|
62 | - return false; |
|
63 | - } |
|
64 | - |
|
65 | - /** |
|
66 | - * {@inheritdoc} |
|
67 | - */ |
|
68 | - public function setVerbosity(int $level) |
|
69 | - { |
|
70 | - // do nothing |
|
71 | - } |
|
72 | - |
|
73 | - /** |
|
74 | - * {@inheritdoc} |
|
75 | - */ |
|
76 | - public function getVerbosity() |
|
77 | - { |
|
78 | - return self::VERBOSITY_QUIET; |
|
79 | - } |
|
80 | - |
|
81 | - /** |
|
82 | - * {@inheritdoc} |
|
83 | - */ |
|
84 | - public function isQuiet() |
|
85 | - { |
|
86 | - return true; |
|
87 | - } |
|
88 | - |
|
89 | - /** |
|
90 | - * {@inheritdoc} |
|
91 | - */ |
|
92 | - public function isVerbose() |
|
93 | - { |
|
94 | - return false; |
|
95 | - } |
|
96 | - |
|
97 | - /** |
|
98 | - * {@inheritdoc} |
|
99 | - */ |
|
100 | - public function isVeryVerbose() |
|
101 | - { |
|
102 | - return false; |
|
103 | - } |
|
104 | - |
|
105 | - /** |
|
106 | - * {@inheritdoc} |
|
107 | - */ |
|
108 | - public function isDebug() |
|
109 | - { |
|
110 | - return false; |
|
111 | - } |
|
112 | - |
|
113 | - /** |
|
114 | - * {@inheritdoc} |
|
115 | - */ |
|
116 | - public function writeln($messages, int $options = self::OUTPUT_NORMAL) |
|
117 | - { |
|
118 | - // do nothing |
|
119 | - } |
|
120 | - |
|
121 | - /** |
|
122 | - * {@inheritdoc} |
|
123 | - */ |
|
124 | - public function write($messages, bool $newline = false, int $options = self::OUTPUT_NORMAL) |
|
125 | - { |
|
126 | - // do nothing |
|
127 | - } |
|
27 | + private $formatter; |
|
28 | + |
|
29 | + /** |
|
30 | + * {@inheritdoc} |
|
31 | + */ |
|
32 | + public function setFormatter(OutputFormatterInterface $formatter) |
|
33 | + { |
|
34 | + // do nothing |
|
35 | + } |
|
36 | + |
|
37 | + /** |
|
38 | + * {@inheritdoc} |
|
39 | + */ |
|
40 | + public function getFormatter() |
|
41 | + { |
|
42 | + if ($this->formatter) { |
|
43 | + return $this->formatter; |
|
44 | + } |
|
45 | + // to comply with the interface we must return a OutputFormatterInterface |
|
46 | + return $this->formatter = new NullOutputFormatter(); |
|
47 | + } |
|
48 | + |
|
49 | + /** |
|
50 | + * {@inheritdoc} |
|
51 | + */ |
|
52 | + public function setDecorated(bool $decorated) |
|
53 | + { |
|
54 | + // do nothing |
|
55 | + } |
|
56 | + |
|
57 | + /** |
|
58 | + * {@inheritdoc} |
|
59 | + */ |
|
60 | + public function isDecorated() |
|
61 | + { |
|
62 | + return false; |
|
63 | + } |
|
64 | + |
|
65 | + /** |
|
66 | + * {@inheritdoc} |
|
67 | + */ |
|
68 | + public function setVerbosity(int $level) |
|
69 | + { |
|
70 | + // do nothing |
|
71 | + } |
|
72 | + |
|
73 | + /** |
|
74 | + * {@inheritdoc} |
|
75 | + */ |
|
76 | + public function getVerbosity() |
|
77 | + { |
|
78 | + return self::VERBOSITY_QUIET; |
|
79 | + } |
|
80 | + |
|
81 | + /** |
|
82 | + * {@inheritdoc} |
|
83 | + */ |
|
84 | + public function isQuiet() |
|
85 | + { |
|
86 | + return true; |
|
87 | + } |
|
88 | + |
|
89 | + /** |
|
90 | + * {@inheritdoc} |
|
91 | + */ |
|
92 | + public function isVerbose() |
|
93 | + { |
|
94 | + return false; |
|
95 | + } |
|
96 | + |
|
97 | + /** |
|
98 | + * {@inheritdoc} |
|
99 | + */ |
|
100 | + public function isVeryVerbose() |
|
101 | + { |
|
102 | + return false; |
|
103 | + } |
|
104 | + |
|
105 | + /** |
|
106 | + * {@inheritdoc} |
|
107 | + */ |
|
108 | + public function isDebug() |
|
109 | + { |
|
110 | + return false; |
|
111 | + } |
|
112 | + |
|
113 | + /** |
|
114 | + * {@inheritdoc} |
|
115 | + */ |
|
116 | + public function writeln($messages, int $options = self::OUTPUT_NORMAL) |
|
117 | + { |
|
118 | + // do nothing |
|
119 | + } |
|
120 | + |
|
121 | + /** |
|
122 | + * {@inheritdoc} |
|
123 | + */ |
|
124 | + public function write($messages, bool $newline = false, int $options = self::OUTPUT_NORMAL) |
|
125 | + { |
|
126 | + // do nothing |
|
127 | + } |
|
128 | 128 | } |
@@ -21,213 +21,213 @@ |
||
21 | 21 | */ |
22 | 22 | class InputOption |
23 | 23 | { |
24 | - /** |
|
25 | - * Do not accept input for the option (e.g. --yell). This is the default behavior of options. |
|
26 | - */ |
|
27 | - public const VALUE_NONE = 1; |
|
28 | - |
|
29 | - /** |
|
30 | - * A value must be passed when the option is used (e.g. --iterations=5 or -i5). |
|
31 | - */ |
|
32 | - public const VALUE_REQUIRED = 2; |
|
33 | - |
|
34 | - /** |
|
35 | - * The option may or may not have a value (e.g. --yell or --yell=loud). |
|
36 | - */ |
|
37 | - public const VALUE_OPTIONAL = 4; |
|
38 | - |
|
39 | - /** |
|
40 | - * The option accepts multiple values (e.g. --dir=/foo --dir=/bar). |
|
41 | - */ |
|
42 | - public const VALUE_IS_ARRAY = 8; |
|
43 | - |
|
44 | - /** |
|
45 | - * The option may have either positive or negative value (e.g. --ansi or --no-ansi). |
|
46 | - */ |
|
47 | - public const VALUE_NEGATABLE = 16; |
|
48 | - |
|
49 | - private $name; |
|
50 | - private $shortcut; |
|
51 | - private $mode; |
|
52 | - private $default; |
|
53 | - private $description; |
|
54 | - |
|
55 | - /** |
|
56 | - * @param string $name The option name |
|
57 | - * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts |
|
58 | - * @param int|null $mode The option mode: One of the VALUE_* constants |
|
59 | - * @param string $description A description text |
|
60 | - * @param string|bool|int|float|array|null $default The default value (must be null for self::VALUE_NONE) |
|
61 | - * |
|
62 | - * @throws InvalidArgumentException If option mode is invalid or incompatible |
|
63 | - */ |
|
64 | - public function __construct(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null) |
|
65 | - { |
|
66 | - if (str_starts_with($name, '--')) { |
|
67 | - $name = substr($name, 2); |
|
68 | - } |
|
69 | - |
|
70 | - if (empty($name)) { |
|
71 | - throw new InvalidArgumentException('An option name cannot be empty.'); |
|
72 | - } |
|
73 | - |
|
74 | - if (empty($shortcut)) { |
|
75 | - $shortcut = null; |
|
76 | - } |
|
77 | - |
|
78 | - if (null !== $shortcut) { |
|
79 | - if (\is_array($shortcut)) { |
|
80 | - $shortcut = implode('|', $shortcut); |
|
81 | - } |
|
82 | - $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); |
|
83 | - $shortcuts = array_filter($shortcuts); |
|
84 | - $shortcut = implode('|', $shortcuts); |
|
85 | - |
|
86 | - if (empty($shortcut)) { |
|
87 | - throw new InvalidArgumentException('An option shortcut cannot be empty.'); |
|
88 | - } |
|
89 | - } |
|
90 | - |
|
91 | - if (null === $mode) { |
|
92 | - $mode = self::VALUE_NONE; |
|
93 | - } elseif ($mode >= (self::VALUE_NEGATABLE << 1) || $mode < 1) { |
|
94 | - throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); |
|
95 | - } |
|
96 | - |
|
97 | - $this->name = $name; |
|
98 | - $this->shortcut = $shortcut; |
|
99 | - $this->mode = $mode; |
|
100 | - $this->description = $description; |
|
101 | - |
|
102 | - if ($this->isArray() && !$this->acceptValue()) { |
|
103 | - throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); |
|
104 | - } |
|
105 | - if ($this->isNegatable() && $this->acceptValue()) { |
|
106 | - throw new InvalidArgumentException('Impossible to have an option mode VALUE_NEGATABLE if the option also accepts a value.'); |
|
107 | - } |
|
108 | - |
|
109 | - $this->setDefault($default); |
|
110 | - } |
|
111 | - |
|
112 | - /** |
|
113 | - * Returns the option shortcut. |
|
114 | - * |
|
115 | - * @return string|null The shortcut |
|
116 | - */ |
|
117 | - public function getShortcut() |
|
118 | - { |
|
119 | - return $this->shortcut; |
|
120 | - } |
|
121 | - |
|
122 | - /** |
|
123 | - * Returns the option name. |
|
124 | - * |
|
125 | - * @return string The name |
|
126 | - */ |
|
127 | - public function getName() |
|
128 | - { |
|
129 | - return $this->name; |
|
130 | - } |
|
131 | - |
|
132 | - /** |
|
133 | - * Returns true if the option accepts a value. |
|
134 | - * |
|
135 | - * @return bool true if value mode is not self::VALUE_NONE, false otherwise |
|
136 | - */ |
|
137 | - public function acceptValue() |
|
138 | - { |
|
139 | - return $this->isValueRequired() || $this->isValueOptional(); |
|
140 | - } |
|
141 | - |
|
142 | - /** |
|
143 | - * Returns true if the option requires a value. |
|
144 | - * |
|
145 | - * @return bool true if value mode is self::VALUE_REQUIRED, false otherwise |
|
146 | - */ |
|
147 | - public function isValueRequired() |
|
148 | - { |
|
149 | - return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); |
|
150 | - } |
|
151 | - |
|
152 | - /** |
|
153 | - * Returns true if the option takes an optional value. |
|
154 | - * |
|
155 | - * @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise |
|
156 | - */ |
|
157 | - public function isValueOptional() |
|
158 | - { |
|
159 | - return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); |
|
160 | - } |
|
161 | - |
|
162 | - /** |
|
163 | - * Returns true if the option can take multiple values. |
|
164 | - * |
|
165 | - * @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise |
|
166 | - */ |
|
167 | - public function isArray() |
|
168 | - { |
|
169 | - return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); |
|
170 | - } |
|
171 | - |
|
172 | - public function isNegatable(): bool |
|
173 | - { |
|
174 | - return self::VALUE_NEGATABLE === (self::VALUE_NEGATABLE & $this->mode); |
|
175 | - } |
|
176 | - |
|
177 | - /** |
|
178 | - * @param string|bool|int|float|array|null $default |
|
179 | - */ |
|
180 | - public function setDefault($default = null) |
|
181 | - { |
|
182 | - if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { |
|
183 | - throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); |
|
184 | - } |
|
185 | - |
|
186 | - if ($this->isArray()) { |
|
187 | - if (null === $default) { |
|
188 | - $default = []; |
|
189 | - } elseif (!\is_array($default)) { |
|
190 | - throw new LogicException('A default value for an array option must be an array.'); |
|
191 | - } |
|
192 | - } |
|
193 | - |
|
194 | - $this->default = $this->acceptValue() || $this->isNegatable() ? $default : false; |
|
195 | - } |
|
196 | - |
|
197 | - /** |
|
198 | - * Returns the default value. |
|
199 | - * |
|
200 | - * @return string|bool|int|float|array|null |
|
201 | - */ |
|
202 | - public function getDefault() |
|
203 | - { |
|
204 | - return $this->default; |
|
205 | - } |
|
206 | - |
|
207 | - /** |
|
208 | - * Returns the description text. |
|
209 | - * |
|
210 | - * @return string The description text |
|
211 | - */ |
|
212 | - public function getDescription() |
|
213 | - { |
|
214 | - return $this->description; |
|
215 | - } |
|
216 | - |
|
217 | - /** |
|
218 | - * Checks whether the given option equals this one. |
|
219 | - * |
|
220 | - * @return bool |
|
221 | - */ |
|
222 | - public function equals(self $option) |
|
223 | - { |
|
224 | - return $option->getName() === $this->getName() |
|
225 | - && $option->getShortcut() === $this->getShortcut() |
|
226 | - && $option->getDefault() === $this->getDefault() |
|
227 | - && $option->isNegatable() === $this->isNegatable() |
|
228 | - && $option->isArray() === $this->isArray() |
|
229 | - && $option->isValueRequired() === $this->isValueRequired() |
|
230 | - && $option->isValueOptional() === $this->isValueOptional() |
|
231 | - ; |
|
232 | - } |
|
24 | + /** |
|
25 | + * Do not accept input for the option (e.g. --yell). This is the default behavior of options. |
|
26 | + */ |
|
27 | + public const VALUE_NONE = 1; |
|
28 | + |
|
29 | + /** |
|
30 | + * A value must be passed when the option is used (e.g. --iterations=5 or -i5). |
|
31 | + */ |
|
32 | + public const VALUE_REQUIRED = 2; |
|
33 | + |
|
34 | + /** |
|
35 | + * The option may or may not have a value (e.g. --yell or --yell=loud). |
|
36 | + */ |
|
37 | + public const VALUE_OPTIONAL = 4; |
|
38 | + |
|
39 | + /** |
|
40 | + * The option accepts multiple values (e.g. --dir=/foo --dir=/bar). |
|
41 | + */ |
|
42 | + public const VALUE_IS_ARRAY = 8; |
|
43 | + |
|
44 | + /** |
|
45 | + * The option may have either positive or negative value (e.g. --ansi or --no-ansi). |
|
46 | + */ |
|
47 | + public const VALUE_NEGATABLE = 16; |
|
48 | + |
|
49 | + private $name; |
|
50 | + private $shortcut; |
|
51 | + private $mode; |
|
52 | + private $default; |
|
53 | + private $description; |
|
54 | + |
|
55 | + /** |
|
56 | + * @param string $name The option name |
|
57 | + * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts |
|
58 | + * @param int|null $mode The option mode: One of the VALUE_* constants |
|
59 | + * @param string $description A description text |
|
60 | + * @param string|bool|int|float|array|null $default The default value (must be null for self::VALUE_NONE) |
|
61 | + * |
|
62 | + * @throws InvalidArgumentException If option mode is invalid or incompatible |
|
63 | + */ |
|
64 | + public function __construct(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null) |
|
65 | + { |
|
66 | + if (str_starts_with($name, '--')) { |
|
67 | + $name = substr($name, 2); |
|
68 | + } |
|
69 | + |
|
70 | + if (empty($name)) { |
|
71 | + throw new InvalidArgumentException('An option name cannot be empty.'); |
|
72 | + } |
|
73 | + |
|
74 | + if (empty($shortcut)) { |
|
75 | + $shortcut = null; |
|
76 | + } |
|
77 | + |
|
78 | + if (null !== $shortcut) { |
|
79 | + if (\is_array($shortcut)) { |
|
80 | + $shortcut = implode('|', $shortcut); |
|
81 | + } |
|
82 | + $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); |
|
83 | + $shortcuts = array_filter($shortcuts); |
|
84 | + $shortcut = implode('|', $shortcuts); |
|
85 | + |
|
86 | + if (empty($shortcut)) { |
|
87 | + throw new InvalidArgumentException('An option shortcut cannot be empty.'); |
|
88 | + } |
|
89 | + } |
|
90 | + |
|
91 | + if (null === $mode) { |
|
92 | + $mode = self::VALUE_NONE; |
|
93 | + } elseif ($mode >= (self::VALUE_NEGATABLE << 1) || $mode < 1) { |
|
94 | + throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); |
|
95 | + } |
|
96 | + |
|
97 | + $this->name = $name; |
|
98 | + $this->shortcut = $shortcut; |
|
99 | + $this->mode = $mode; |
|
100 | + $this->description = $description; |
|
101 | + |
|
102 | + if ($this->isArray() && !$this->acceptValue()) { |
|
103 | + throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); |
|
104 | + } |
|
105 | + if ($this->isNegatable() && $this->acceptValue()) { |
|
106 | + throw new InvalidArgumentException('Impossible to have an option mode VALUE_NEGATABLE if the option also accepts a value.'); |
|
107 | + } |
|
108 | + |
|
109 | + $this->setDefault($default); |
|
110 | + } |
|
111 | + |
|
112 | + /** |
|
113 | + * Returns the option shortcut. |
|
114 | + * |
|
115 | + * @return string|null The shortcut |
|
116 | + */ |
|
117 | + public function getShortcut() |
|
118 | + { |
|
119 | + return $this->shortcut; |
|
120 | + } |
|
121 | + |
|
122 | + /** |
|
123 | + * Returns the option name. |
|
124 | + * |
|
125 | + * @return string The name |
|
126 | + */ |
|
127 | + public function getName() |
|
128 | + { |
|
129 | + return $this->name; |
|
130 | + } |
|
131 | + |
|
132 | + /** |
|
133 | + * Returns true if the option accepts a value. |
|
134 | + * |
|
135 | + * @return bool true if value mode is not self::VALUE_NONE, false otherwise |
|
136 | + */ |
|
137 | + public function acceptValue() |
|
138 | + { |
|
139 | + return $this->isValueRequired() || $this->isValueOptional(); |
|
140 | + } |
|
141 | + |
|
142 | + /** |
|
143 | + * Returns true if the option requires a value. |
|
144 | + * |
|
145 | + * @return bool true if value mode is self::VALUE_REQUIRED, false otherwise |
|
146 | + */ |
|
147 | + public function isValueRequired() |
|
148 | + { |
|
149 | + return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); |
|
150 | + } |
|
151 | + |
|
152 | + /** |
|
153 | + * Returns true if the option takes an optional value. |
|
154 | + * |
|
155 | + * @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise |
|
156 | + */ |
|
157 | + public function isValueOptional() |
|
158 | + { |
|
159 | + return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); |
|
160 | + } |
|
161 | + |
|
162 | + /** |
|
163 | + * Returns true if the option can take multiple values. |
|
164 | + * |
|
165 | + * @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise |
|
166 | + */ |
|
167 | + public function isArray() |
|
168 | + { |
|
169 | + return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); |
|
170 | + } |
|
171 | + |
|
172 | + public function isNegatable(): bool |
|
173 | + { |
|
174 | + return self::VALUE_NEGATABLE === (self::VALUE_NEGATABLE & $this->mode); |
|
175 | + } |
|
176 | + |
|
177 | + /** |
|
178 | + * @param string|bool|int|float|array|null $default |
|
179 | + */ |
|
180 | + public function setDefault($default = null) |
|
181 | + { |
|
182 | + if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { |
|
183 | + throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); |
|
184 | + } |
|
185 | + |
|
186 | + if ($this->isArray()) { |
|
187 | + if (null === $default) { |
|
188 | + $default = []; |
|
189 | + } elseif (!\is_array($default)) { |
|
190 | + throw new LogicException('A default value for an array option must be an array.'); |
|
191 | + } |
|
192 | + } |
|
193 | + |
|
194 | + $this->default = $this->acceptValue() || $this->isNegatable() ? $default : false; |
|
195 | + } |
|
196 | + |
|
197 | + /** |
|
198 | + * Returns the default value. |
|
199 | + * |
|
200 | + * @return string|bool|int|float|array|null |
|
201 | + */ |
|
202 | + public function getDefault() |
|
203 | + { |
|
204 | + return $this->default; |
|
205 | + } |
|
206 | + |
|
207 | + /** |
|
208 | + * Returns the description text. |
|
209 | + * |
|
210 | + * @return string The description text |
|
211 | + */ |
|
212 | + public function getDescription() |
|
213 | + { |
|
214 | + return $this->description; |
|
215 | + } |
|
216 | + |
|
217 | + /** |
|
218 | + * Checks whether the given option equals this one. |
|
219 | + * |
|
220 | + * @return bool |
|
221 | + */ |
|
222 | + public function equals(self $option) |
|
223 | + { |
|
224 | + return $option->getName() === $this->getName() |
|
225 | + && $option->getShortcut() === $this->getShortcut() |
|
226 | + && $option->getDefault() === $this->getDefault() |
|
227 | + && $option->isNegatable() === $this->isNegatable() |
|
228 | + && $option->isArray() === $this->isArray() |
|
229 | + && $option->isValueRequired() === $this->isValueRequired() |
|
230 | + && $option->isValueOptional() === $this->isValueOptional() |
|
231 | + ; |
|
232 | + } |
|
233 | 233 | } |
@@ -24,45 +24,45 @@ |
||
24 | 24 | */ |
25 | 25 | class StringInput extends ArgvInput |
26 | 26 | { |
27 | - public const REGEX_STRING = '([^\s]+?)(?:\s|(?<!\\\\)"|(?<!\\\\)\'|$)'; |
|
28 | - public const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')'; |
|
27 | + public const REGEX_STRING = '([^\s]+?)(?:\s|(?<!\\\\)"|(?<!\\\\)\'|$)'; |
|
28 | + public const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')'; |
|
29 | 29 | |
30 | - /** |
|
31 | - * @param string $input A string representing the parameters from the CLI |
|
32 | - */ |
|
33 | - public function __construct(string $input) |
|
34 | - { |
|
35 | - parent::__construct([]); |
|
30 | + /** |
|
31 | + * @param string $input A string representing the parameters from the CLI |
|
32 | + */ |
|
33 | + public function __construct(string $input) |
|
34 | + { |
|
35 | + parent::__construct([]); |
|
36 | 36 | |
37 | - $this->setTokens($this->tokenize($input)); |
|
38 | - } |
|
37 | + $this->setTokens($this->tokenize($input)); |
|
38 | + } |
|
39 | 39 | |
40 | - /** |
|
41 | - * Tokenizes a string. |
|
42 | - * |
|
43 | - * @throws InvalidArgumentException When unable to parse input (should never happen) |
|
44 | - */ |
|
45 | - private function tokenize(string $input): array |
|
46 | - { |
|
47 | - $tokens = []; |
|
48 | - $length = \strlen($input); |
|
49 | - $cursor = 0; |
|
50 | - while ($cursor < $length) { |
|
51 | - if (preg_match('/\s+/A', $input, $match, 0, $cursor)) { |
|
52 | - } elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, 0, $cursor)) { |
|
53 | - $tokens[] = $match[1].$match[2].stripcslashes(str_replace(['"\'', '\'"', '\'\'', '""'], '', substr($match[3], 1, -1))); |
|
54 | - } elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, 0, $cursor)) { |
|
55 | - $tokens[] = stripcslashes(substr($match[0], 1, -1)); |
|
56 | - } elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, 0, $cursor)) { |
|
57 | - $tokens[] = stripcslashes($match[1]); |
|
58 | - } else { |
|
59 | - // should never happen |
|
60 | - throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ...".', substr($input, $cursor, 10))); |
|
61 | - } |
|
40 | + /** |
|
41 | + * Tokenizes a string. |
|
42 | + * |
|
43 | + * @throws InvalidArgumentException When unable to parse input (should never happen) |
|
44 | + */ |
|
45 | + private function tokenize(string $input): array |
|
46 | + { |
|
47 | + $tokens = []; |
|
48 | + $length = \strlen($input); |
|
49 | + $cursor = 0; |
|
50 | + while ($cursor < $length) { |
|
51 | + if (preg_match('/\s+/A', $input, $match, 0, $cursor)) { |
|
52 | + } elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, 0, $cursor)) { |
|
53 | + $tokens[] = $match[1].$match[2].stripcslashes(str_replace(['"\'', '\'"', '\'\'', '""'], '', substr($match[3], 1, -1))); |
|
54 | + } elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, 0, $cursor)) { |
|
55 | + $tokens[] = stripcslashes(substr($match[0], 1, -1)); |
|
56 | + } elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, 0, $cursor)) { |
|
57 | + $tokens[] = stripcslashes($match[1]); |
|
58 | + } else { |
|
59 | + // should never happen |
|
60 | + throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ...".', substr($input, $cursor, 10))); |
|
61 | + } |
|
62 | 62 | |
63 | - $cursor += \strlen($match[0]); |
|
64 | - } |
|
63 | + $cursor += \strlen($match[0]); |
|
64 | + } |
|
65 | 65 | |
66 | - return $tokens; |
|
67 | - } |
|
66 | + return $tokens; |
|
67 | + } |
|
68 | 68 | } |