Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\Console\Formatter; Chris@0: Chris@0: use Symfony\Component\Console\Exception\InvalidArgumentException; Chris@0: Chris@0: /** Chris@0: * Formatter class for console output. Chris@0: * Chris@0: * @author Konstantin Kudryashov Chris@0: */ Chris@0: class OutputFormatter implements OutputFormatterInterface Chris@0: { Chris@0: private $decorated; Chris@17: private $styles = []; Chris@0: private $styleStack; Chris@0: Chris@0: /** Chris@0: * Escapes "<" special char in given text. Chris@0: * Chris@0: * @param string $text Text to escape Chris@0: * Chris@0: * @return string Escaped text Chris@0: */ Chris@0: public static function escape($text) Chris@0: { Chris@0: $text = preg_replace('/([^\\\\]?) FormatterStyle" instances Chris@0: */ Chris@17: public function __construct($decorated = false, array $styles = []) Chris@0: { Chris@0: $this->decorated = (bool) $decorated; Chris@0: Chris@0: $this->setStyle('error', new OutputFormatterStyle('white', 'red')); Chris@0: $this->setStyle('info', new OutputFormatterStyle('green')); Chris@0: $this->setStyle('comment', new OutputFormatterStyle('yellow')); Chris@0: $this->setStyle('question', new OutputFormatterStyle('black', 'cyan')); Chris@0: Chris@0: foreach ($styles as $name => $style) { Chris@0: $this->setStyle($name, $style); Chris@0: } Chris@0: Chris@0: $this->styleStack = new OutputFormatterStyleStack(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setDecorated($decorated) Chris@0: { Chris@0: $this->decorated = (bool) $decorated; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function isDecorated() Chris@0: { Chris@0: return $this->decorated; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function setStyle($name, OutputFormatterStyleInterface $style) Chris@0: { Chris@0: $this->styles[strtolower($name)] = $style; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function hasStyle($name) Chris@0: { Chris@0: return isset($this->styles[strtolower($name)]); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getStyle($name) Chris@0: { Chris@0: if (!$this->hasStyle($name)) { Chris@0: throw new InvalidArgumentException(sprintf('Undefined style: %s', $name)); Chris@0: } Chris@0: Chris@0: return $this->styles[strtolower($name)]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function format($message) Chris@0: { Chris@0: $message = (string) $message; Chris@0: $offset = 0; Chris@0: $output = ''; Chris@0: $tagRegex = '[a-z][a-z0-9,_=;-]*+'; Chris@0: preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#ix", $message, $matches, PREG_OFFSET_CAPTURE); Chris@0: foreach ($matches[0] as $i => $match) { Chris@0: $pos = $match[1]; Chris@0: $text = $match[0]; Chris@0: Chris@0: if (0 != $pos && '\\' == $message[$pos - 1]) { Chris@0: continue; Chris@0: } Chris@0: Chris@0: // add the text up to the next tag Chris@0: $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset)); Chris@17: $offset = $pos + \strlen($text); Chris@0: Chris@0: // opening tag? Chris@0: if ($open = '/' != $text[1]) { Chris@0: $tag = $matches[1][$i][0]; Chris@0: } else { Chris@0: $tag = isset($matches[3][$i][0]) ? $matches[3][$i][0] : ''; Chris@0: } Chris@0: Chris@0: if (!$open && !$tag) { Chris@0: // Chris@0: $this->styleStack->pop(); Chris@17: } elseif (false === $style = $this->createStyleFromString($tag)) { Chris@0: $output .= $this->applyCurrentStyle($text); Chris@0: } elseif ($open) { Chris@0: $this->styleStack->push($style); Chris@0: } else { Chris@0: $this->styleStack->pop($style); Chris@0: } Chris@0: } Chris@0: Chris@0: $output .= $this->applyCurrentStyle(substr($message, $offset)); Chris@0: Chris@14: if (false !== strpos($output, "\0")) { Chris@17: return strtr($output, ["\0" => '\\', '\\<' => '<']); Chris@0: } Chris@0: Chris@0: return str_replace('\\<', '<', $output); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return OutputFormatterStyleStack Chris@0: */ Chris@0: public function getStyleStack() Chris@0: { Chris@0: return $this->styleStack; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tries to create new style instance from string. Chris@0: * Chris@0: * @param string $string Chris@0: * Chris@0: * @return OutputFormatterStyle|false false if string is not format string Chris@0: */ Chris@0: private function createStyleFromString($string) Chris@0: { Chris@0: if (isset($this->styles[$string])) { Chris@0: return $this->styles[$string]; Chris@0: } Chris@0: Chris@0: if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', $string, $matches, PREG_SET_ORDER)) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: $style = new OutputFormatterStyle(); Chris@0: foreach ($matches as $match) { Chris@0: array_shift($match); Chris@17: $match[0] = strtolower($match[0]); Chris@0: Chris@0: if ('fg' == $match[0]) { Chris@17: $style->setForeground(strtolower($match[1])); Chris@0: } elseif ('bg' == $match[0]) { Chris@17: $style->setBackground(strtolower($match[1])); Chris@0: } elseif ('options' === $match[0]) { Chris@17: preg_match_all('([^,;]+)', strtolower($match[1]), $options); Chris@0: $options = array_shift($options); Chris@0: foreach ($options as $option) { Chris@0: try { Chris@0: $style->setOption($option); Chris@0: } catch (\InvalidArgumentException $e) { Chris@14: @trigger_error(sprintf('Unknown style options are deprecated since Symfony 3.2 and will be removed in 4.0. Exception "%s".', $e->getMessage()), E_USER_DEPRECATED); Chris@0: Chris@0: return false; Chris@0: } Chris@0: } Chris@0: } else { Chris@0: return false; Chris@0: } Chris@0: } Chris@0: Chris@0: return $style; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Applies current style from stack to text, if must be applied. Chris@0: * Chris@0: * @param string $text Input text Chris@0: * Chris@0: * @return string Styled text Chris@0: */ Chris@0: private function applyCurrentStyle($text) Chris@0: { Chris@17: return $this->isDecorated() && \strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text; Chris@0: } Chris@0: }