1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace Symfony\Component\Console\Formatter;
13:
14: 15: 16: 17: 18: 19: 20:
21: class OutputFormatter implements OutputFormatterInterface
22: {
23: 24: 25:
26: const FORMAT_PATTERN = '#(\\\\?)<(/?)([a-z][a-z0-9_=;-]+)?>((?: [^<\\\\]+ | (?!<(?:/?[a-z]|/>)). | .(?<=\\\\<) )*)#isx';
27:
28: private $decorated;
29: private $styles = array();
30: private $styleStack;
31:
32: 33: 34: 35: 36: 37: 38:
39: public static function escape($text)
40: {
41: return preg_replace('/([^\\\\]?)</is', '$1\\<', $text);
42: }
43:
44: 45: 46: 47: 48: 49: 50: 51:
52: public function __construct($decorated = null, array $styles = array())
53: {
54: $this->decorated = (Boolean) $decorated;
55:
56: $this->setStyle('error', new OutputFormatterStyle('white', 'red'));
57: $this->setStyle('info', new OutputFormatterStyle('green'));
58: $this->setStyle('comment', new OutputFormatterStyle('yellow'));
59: $this->setStyle('question', new OutputFormatterStyle('black', 'cyan'));
60:
61: foreach ($styles as $name => $style) {
62: $this->setStyle($name, $style);
63: }
64:
65: $this->styleStack = new OutputFormatterStyleStack();
66: }
67:
68: 69: 70: 71: 72: 73: 74:
75: public function setDecorated($decorated)
76: {
77: $this->decorated = (Boolean) $decorated;
78: }
79:
80: 81: 82: 83: 84: 85: 86:
87: public function isDecorated()
88: {
89: return $this->decorated;
90: }
91:
92: 93: 94: 95: 96: 97: 98: 99:
100: public function setStyle($name, OutputFormatterStyleInterface $style)
101: {
102: $this->styles[strtolower($name)] = $style;
103: }
104:
105: 106: 107: 108: 109: 110: 111: 112: 113:
114: public function hasStyle($name)
115: {
116: return isset($this->styles[strtolower($name)]);
117: }
118:
119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129:
130: public function getStyle($name)
131: {
132: if (!$this->hasStyle($name)) {
133: throw new \InvalidArgumentException('Undefined style: '.$name);
134: }
135:
136: return $this->styles[strtolower($name)];
137: }
138:
139: 140: 141: 142: 143: 144: 145: 146: 147:
148: public function format($message)
149: {
150: $message = preg_replace_callback(self::FORMAT_PATTERN, array($this, 'replaceStyle'), $message);
151:
152: return str_replace('\\<', '<', $message);
153: }
154:
155: 156: 157:
158: public function getStyleStack()
159: {
160: return $this->styleStack;
161: }
162:
163: 164: 165: 166: 167: 168: 169:
170: private function replaceStyle($match)
171: {
172:
173: if ('\\' === $match[1]) {
174: return $this->applyCurrentStyle($match[0]);
175: }
176:
177: if ('' === $match[3]) {
178: if ('/' === $match[2]) {
179:
180: $this->styleStack->pop();
181:
182: return $this->applyCurrentStyle($match[4]);
183: }
184:
185:
186: return '<>'.$this->applyCurrentStyle($match[4]);
187: }
188:
189: if (isset($this->styles[strtolower($match[3])])) {
190: $style = $this->styles[strtolower($match[3])];
191: } else {
192: $style = $this->createStyleFromString($match[3]);
193:
194: if (false === $style) {
195: return $this->applyCurrentStyle($match[0]);
196: }
197: }
198:
199: if ('/' === $match[2]) {
200: $this->styleStack->pop($style);
201: } else {
202: $this->styleStack->push($style);
203: }
204:
205: return $this->applyCurrentStyle($match[4]);
206: }
207:
208: 209: 210: 211: 212: 213: 214:
215: private function createStyleFromString($string)
216: {
217: if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', strtolower($string), $matches, PREG_SET_ORDER)) {
218: return false;
219: }
220:
221: $style = new OutputFormatterStyle();
222: foreach ($matches as $match) {
223: array_shift($match);
224:
225: if ('fg' == $match[0]) {
226: $style->setForeground($match[1]);
227: } elseif ('bg' == $match[0]) {
228: $style->setBackground($match[1]);
229: } else {
230: $style->setOption($match[1]);
231: }
232: }
233:
234: return $style;
235: }
236:
237: 238: 239: 240: 241: 242: 243:
244: private function applyCurrentStyle($text)
245: {
246: return $this->isDecorated() && strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text;
247: }
248: }
249: