Projekt

Obecné

Profil

Stáhnout (35.8 KB) Statistiky
| Větev: | Revize:
1
<?php
2

    
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <fabien@symfony.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11

    
12
namespace Symfony\Component\Console;
13

    
14
use Symfony\Component\Console\Exception\ExceptionInterface;
15
use Symfony\Component\Console\Helper\DebugFormatterHelper;
16
use Symfony\Component\Console\Helper\ProcessHelper;
17
use Symfony\Component\Console\Helper\QuestionHelper;
18
use Symfony\Component\Console\Input\InputInterface;
19
use Symfony\Component\Console\Input\ArgvInput;
20
use Symfony\Component\Console\Input\ArrayInput;
21
use Symfony\Component\Console\Input\InputDefinition;
22
use Symfony\Component\Console\Input\InputOption;
23
use Symfony\Component\Console\Input\InputArgument;
24
use Symfony\Component\Console\Input\InputAwareInterface;
25
use Symfony\Component\Console\Output\OutputInterface;
26
use Symfony\Component\Console\Output\ConsoleOutput;
27
use Symfony\Component\Console\Output\ConsoleOutputInterface;
28
use Symfony\Component\Console\Command\Command;
29
use Symfony\Component\Console\Command\HelpCommand;
30
use Symfony\Component\Console\Command\ListCommand;
31
use Symfony\Component\Console\Helper\HelperSet;
32
use Symfony\Component\Console\Helper\FormatterHelper;
33
use Symfony\Component\Console\Event\ConsoleCommandEvent;
34
use Symfony\Component\Console\Event\ConsoleExceptionEvent;
35
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
36
use Symfony\Component\Console\Exception\CommandNotFoundException;
37
use Symfony\Component\Console\Exception\LogicException;
38
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
39

    
40
/**
41
 * An Application is the container for a collection of commands.
42
 *
43
 * It is the main entry point of a Console application.
44
 *
45
 * This class is optimized for a standard CLI environment.
46
 *
47
 * Usage:
48
 *
49
 *     $app = new Application('myapp', '1.0 (stable)');
50
 *     $app->add(new SimpleCommand());
51
 *     $app->run();
52
 *
53
 * @author Fabien Potencier <fabien@symfony.com>
54
 */
55
class Application
56
{
57
    private $commands = array();
58
    private $wantHelps = false;
59
    private $runningCommand;
60
    private $name;
61
    private $version;
62
    private $catchExceptions = true;
63
    private $autoExit = true;
64
    private $definition;
65
    private $helperSet;
66
    private $dispatcher;
67
    private $terminalDimensions;
68
    private $defaultCommand;
69

    
70
    /**
71
     * Constructor.
72
     *
73
     * @param string $name    The name of the application
74
     * @param string $version The version of the application
75
     */
76
    public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
77
    {
78
        $this->name = $name;
79
        $this->version = $version;
80
        $this->defaultCommand = 'list';
81
        $this->helperSet = $this->getDefaultHelperSet();
82
        $this->definition = $this->getDefaultInputDefinition();
83

    
84
        foreach ($this->getDefaultCommands() as $command) {
85
            $this->add($command);
86
        }
87
    }
88

    
89
    public function setDispatcher(EventDispatcherInterface $dispatcher)
90
    {
91
        $this->dispatcher = $dispatcher;
92
    }
93

    
94
    /**
95
     * Runs the current application.
96
     *
97
     * @param InputInterface  $input  An Input instance
98
     * @param OutputInterface $output An Output instance
99
     *
100
     * @return int 0 if everything went fine, or an error code
101
     *
102
     * @throws \Exception When doRun returns Exception
103
     */
104
    public function run(InputInterface $input = null, OutputInterface $output = null)
105
    {
106
        if (null === $input) {
107
            $input = new ArgvInput();
108
        }
109

    
110
        if (null === $output) {
111
            $output = new ConsoleOutput();
112
        }
113

    
114
        $this->configureIO($input, $output);
115

    
116
        try {
117
            $exitCode = $this->doRun($input, $output);
118
        } catch (\Exception $e) {
119
            if (!$this->catchExceptions) {
120
                throw $e;
121
            }
122

    
123
            if ($output instanceof ConsoleOutputInterface) {
124
                $this->renderException($e, $output->getErrorOutput());
125
            } else {
126
                $this->renderException($e, $output);
127
            }
128

    
129
            $exitCode = $e->getCode();
130
            if (is_numeric($exitCode)) {
131
                $exitCode = (int) $exitCode;
132
                if (0 === $exitCode) {
133
                    $exitCode = 1;
134
                }
135
            } else {
136
                $exitCode = 1;
137
            }
138
        }
139

    
140
        if ($this->autoExit) {
141
            if ($exitCode > 255) {
142
                $exitCode = 255;
143
            }
144

    
145
            exit($exitCode);
146
        }
147

    
148
        return $exitCode;
149
    }
150

    
151
    /**
152
     * Runs the current application.
153
     *
154
     * @param InputInterface  $input  An Input instance
155
     * @param OutputInterface $output An Output instance
156
     *
157
     * @return int 0 if everything went fine, or an error code
158
     */
159
    public function doRun(InputInterface $input, OutputInterface $output)
160
    {
161
        if (true === $input->hasParameterOption(array('--version', '-V'), true)) {
162
            $output->writeln($this->getLongVersion());
163

    
164
            return 0;
165
        }
166

    
167
        $name = $this->getCommandName($input);
168
        if (true === $input->hasParameterOption(array('--help', '-h'), true)) {
169
            if (!$name) {
170
                $name = 'help';
171
                $input = new ArrayInput(array('command' => 'help'));
172
            } else {
173
                $this->wantHelps = true;
174
            }
175
        }
176

    
177
        if (!$name) {
178
            $name = $this->defaultCommand;
179
            $input = new ArrayInput(array('command' => $this->defaultCommand));
180
        }
181

    
182
        // the command name MUST be the first element of the input
183
        $command = $this->find($name);
184

    
185
        $this->runningCommand = $command;
186
        $exitCode = $this->doRunCommand($command, $input, $output);
187
        $this->runningCommand = null;
188

    
189
        return $exitCode;
190
    }
191

    
192
    /**
193
     * Set a helper set to be used with the command.
194
     *
195
     * @param HelperSet $helperSet The helper set
196
     */
197
    public function setHelperSet(HelperSet $helperSet)
198
    {
199
        $this->helperSet = $helperSet;
200
    }
201

    
202
    /**
203
     * Get the helper set associated with the command.
204
     *
205
     * @return HelperSet The HelperSet instance associated with this command
206
     */
207
    public function getHelperSet()
208
    {
209
        return $this->helperSet;
210
    }
211

    
212
    /**
213
     * Set an input definition to be used with this application.
214
     *
215
     * @param InputDefinition $definition The input definition
216
     */
217
    public function setDefinition(InputDefinition $definition)
218
    {
219
        $this->definition = $definition;
220
    }
221

    
222
    /**
223
     * Gets the InputDefinition related to this Application.
224
     *
225
     * @return InputDefinition The InputDefinition instance
226
     */
227
    public function getDefinition()
228
    {
229
        return $this->definition;
230
    }
231

    
232
    /**
233
     * Gets the help message.
234
     *
235
     * @return string A help message
236
     */
237
    public function getHelp()
238
    {
239
        return $this->getLongVersion();
240
    }
241

    
242
    /**
243
     * Sets whether to catch exceptions or not during commands execution.
244
     *
245
     * @param bool $boolean Whether to catch exceptions or not during commands execution
246
     */
247
    public function setCatchExceptions($boolean)
248
    {
249
        $this->catchExceptions = (bool) $boolean;
250
    }
251

    
252
    /**
253
     * Sets whether to automatically exit after a command execution or not.
254
     *
255
     * @param bool $boolean Whether to automatically exit after a command execution or not
256
     */
257
    public function setAutoExit($boolean)
258
    {
259
        $this->autoExit = (bool) $boolean;
260
    }
261

    
262
    /**
263
     * Gets the name of the application.
264
     *
265
     * @return string The application name
266
     */
267
    public function getName()
268
    {
269
        return $this->name;
270
    }
271

    
272
    /**
273
     * Sets the application name.
274
     *
275
     * @param string $name The application name
276
     */
277
    public function setName($name)
278
    {
279
        $this->name = $name;
280
    }
281

    
282
    /**
283
     * Gets the application version.
284
     *
285
     * @return string The application version
286
     */
287
    public function getVersion()
288
    {
289
        return $this->version;
290
    }
291

    
292
    /**
293
     * Sets the application version.
294
     *
295
     * @param string $version The application version
296
     */
297
    public function setVersion($version)
298
    {
299
        $this->version = $version;
300
    }
301

    
302
    /**
303
     * Returns the long version of the application.
304
     *
305
     * @return string The long application version
306
     */
307
    public function getLongVersion()
308
    {
309
        if ('UNKNOWN' !== $this->getName()) {
310
            if ('UNKNOWN' !== $this->getVersion()) {
311
                return sprintf('<info>%s</info> version <comment>%s</comment>', $this->getName(), $this->getVersion());
312
            }
313

    
314
            return sprintf('<info>%s</info>', $this->getName());
315
        }
316

    
317
        return '<info>Console Tool</info>';
318
    }
319

    
320
    /**
321
     * Registers a new command.
322
     *
323
     * @param string $name The command name
324
     *
325
     * @return Command The newly created command
326
     */
327
    public function register($name)
328
    {
329
        return $this->add(new Command($name));
330
    }
331

    
332
    /**
333
     * Adds an array of command objects.
334
     *
335
     * If a Command is not enabled it will not be added.
336
     * 
337
     * @param Command[] $commands An array of commands
338
     */
339
    public function addCommands(array $commands)
340
    {
341
        foreach ($commands as $command) {
342
            $this->add($command);
343
        }
344
    }
345

    
346
    /**
347
     * Adds a command object.
348
     *
349
     * If a command with the same name already exists, it will be overridden.
350
     * If the command is not enabled it will not be added.
351
     *
352
     * @param Command $command A Command object
353
     *
354
     * @return Command|null The registered command if enabled or null
355
     */
356
    public function add(Command $command)
357
    {
358
        $command->setApplication($this);
359

    
360
        if (!$command->isEnabled()) {
361
            $command->setApplication(null);
362

    
363
            return;
364
        }
365

    
366
        if (null === $command->getDefinition()) {
367
            throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)));
368
        }
369

    
370
        $this->commands[$command->getName()] = $command;
371

    
372
        foreach ($command->getAliases() as $alias) {
373
            $this->commands[$alias] = $command;
374
        }
375

    
376
        return $command;
377
    }
378

    
379
    /**
380
     * Returns a registered command by name or alias.
381
     *
382
     * @param string $name The command name or alias
383
     *
384
     * @return Command A Command object
385
     *
386
     * @throws CommandNotFoundException When command name given does not exist
387
     */
388
    public function get($name)
389
    {
390
        if (!isset($this->commands[$name])) {
391
            throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));
392
        }
393

    
394
        $command = $this->commands[$name];
395

    
396
        if ($this->wantHelps) {
397
            $this->wantHelps = false;
398

    
399
            $helpCommand = $this->get('help');
400
            $helpCommand->setCommand($command);
401

    
402
            return $helpCommand;
403
        }
404

    
405
        return $command;
406
    }
407

    
408
    /**
409
     * Returns true if the command exists, false otherwise.
410
     *
411
     * @param string $name The command name or alias
412
     *
413
     * @return bool true if the command exists, false otherwise
414
     */
415
    public function has($name)
416
    {
417
        return isset($this->commands[$name]);
418
    }
419

    
420
    /**
421
     * Returns an array of all unique namespaces used by currently registered commands.
422
     *
423
     * It does not return the global namespace which always exists.
424
     *
425
     * @return string[] An array of namespaces
426
     */
427
    public function getNamespaces()
428
    {
429
        $namespaces = array();
430
        foreach ($this->all() as $command) {
431
            $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));
432

    
433
            foreach ($command->getAliases() as $alias) {
434
                $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias));
435
            }
436
        }
437

    
438
        return array_values(array_unique(array_filter($namespaces)));
439
    }
440

    
441
    /**
442
     * Finds a registered namespace by a name or an abbreviation.
443
     *
444
     * @param string $namespace A namespace or abbreviation to search for
445
     *
446
     * @return string A registered namespace
447
     *
448
     * @throws CommandNotFoundException When namespace is incorrect or ambiguous
449
     */
450
    public function findNamespace($namespace)
451
    {
452
        $allNamespaces = $this->getNamespaces();
453
        $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $namespace);
454
        $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces);
455

    
456
        if (empty($namespaces)) {
457
            $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
458

    
459
            if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) {
460
                if (1 == count($alternatives)) {
461
                    $message .= "\n\nDid you mean this?\n    ";
462
                } else {
463
                    $message .= "\n\nDid you mean one of these?\n    ";
464
                }
465

    
466
                $message .= implode("\n    ", $alternatives);
467
            }
468

    
469
            throw new CommandNotFoundException($message, $alternatives);
470
        }
471

    
472
        $exact = in_array($namespace, $namespaces, true);
473
        if (count($namespaces) > 1 && !$exact) {
474
            throw new CommandNotFoundException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces));
475
        }
476

    
477
        return $exact ? $namespace : reset($namespaces);
478
    }
479

    
480
    /**
481
     * Finds a command by name or alias.
482
     *
483
     * Contrary to get, this command tries to find the best
484
     * match if you give it an abbreviation of a name or alias.
485
     *
486
     * @param string $name A command name or a command alias
487
     *
488
     * @return Command A Command instance
489
     *
490
     * @throws CommandNotFoundException When command name is incorrect or ambiguous
491
     */
492
    public function find($name)
493
    {
494
        $allCommands = array_keys($this->commands);
495
        $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name);
496
        $commands = preg_grep('{^'.$expr.'}', $allCommands);
497

    
498
        if (empty($commands) || count(preg_grep('{^'.$expr.'$}', $commands)) < 1) {
499
            if (false !== $pos = strrpos($name, ':')) {
500
                // check if a namespace exists and contains commands
501
                $this->findNamespace(substr($name, 0, $pos));
502
            }
503

    
504
            $message = sprintf('Command "%s" is not defined.', $name);
505

    
506
            if ($alternatives = $this->findAlternatives($name, $allCommands)) {
507
                if (1 == count($alternatives)) {
508
                    $message .= "\n\nDid you mean this?\n    ";
509
                } else {
510
                    $message .= "\n\nDid you mean one of these?\n    ";
511
                }
512
                $message .= implode("\n    ", $alternatives);
513
            }
514

    
515
            throw new CommandNotFoundException($message, $alternatives);
516
        }
517

    
518
        // filter out aliases for commands which are already on the list
519
        if (count($commands) > 1) {
520
            $commandList = $this->commands;
521
            $commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) {
522
                $commandName = $commandList[$nameOrAlias]->getName();
523

    
524
                return $commandName === $nameOrAlias || !in_array($commandName, $commands);
525
            });
526
        }
527

    
528
        $exact = in_array($name, $commands, true);
529
        if (count($commands) > 1 && !$exact) {
530
            $suggestions = $this->getAbbreviationSuggestions(array_values($commands));
531

    
532
            throw new CommandNotFoundException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions), array_values($commands));
533
        }
534

    
535
        return $this->get($exact ? $name : reset($commands));
536
    }
537

    
538
    /**
539
     * Gets the commands (registered in the given namespace if provided).
540
     *
541
     * The array keys are the full names and the values the command instances.
542
     *
543
     * @param string $namespace A namespace name
544
     *
545
     * @return Command[] An array of Command instances
546
     */
547
    public function all($namespace = null)
548
    {
549
        if (null === $namespace) {
550
            return $this->commands;
551
        }
552

    
553
        $commands = array();
554
        foreach ($this->commands as $name => $command) {
555
            if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) {
556
                $commands[$name] = $command;
557
            }
558
        }
559

    
560
        return $commands;
561
    }
562

    
563
    /**
564
     * Returns an array of possible abbreviations given a set of names.
565
     *
566
     * @param array $names An array of names
567
     *
568
     * @return array An array of abbreviations
569
     */
570
    public static function getAbbreviations($names)
571
    {
572
        $abbrevs = array();
573
        foreach ($names as $name) {
574
            for ($len = strlen($name); $len > 0; --$len) {
575
                $abbrev = substr($name, 0, $len);
576
                $abbrevs[$abbrev][] = $name;
577
            }
578
        }
579

    
580
        return $abbrevs;
581
    }
582

    
583
    /**
584
     * Renders a caught exception.
585
     *
586
     * @param \Exception      $e      An exception instance
587
     * @param OutputInterface $output An OutputInterface instance
588
     */
589
    public function renderException(\Exception $e, OutputInterface $output)
590
    {
591
        $output->writeln('', OutputInterface::VERBOSITY_QUIET);
592

    
593
        do {
594
            $title = sprintf('  [%s]  ', get_class($e));
595

    
596
            $len = $this->stringWidth($title);
597

    
598
            $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX;
599
            // HHVM only accepts 32 bits integer in str_split, even when PHP_INT_MAX is a 64 bit integer: https://github.com/facebook/hhvm/issues/1327
600
            if (defined('HHVM_VERSION') && $width > 1 << 31) {
601
                $width = 1 << 31;
602
            }
603
            $formatter = $output->getFormatter();
604
            $lines = array();
605
            foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) {
606
                foreach ($this->splitStringByWidth($line, $width - 4) as $line) {
607
                    // pre-format lines to get the right string length
608
                    $lineLength = $this->stringWidth(preg_replace('/\[[^m]*m/', '', $formatter->format($line))) + 4;
609
                    $lines[] = array($line, $lineLength);
610

    
611
                    $len = max($lineLength, $len);
612
                }
613
            }
614

    
615
            $messages = array();
616
            $messages[] = $emptyLine = $formatter->format(sprintf('<error>%s</error>', str_repeat(' ', $len)));
617
            $messages[] = $formatter->format(sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title)))));
618
            foreach ($lines as $line) {
619
                $messages[] = $formatter->format(sprintf('<error>  %s  %s</error>', $line[0], str_repeat(' ', $len - $line[1])));
620
            }
621
            $messages[] = $emptyLine;
622
            $messages[] = '';
623

    
624
            $output->writeln($messages, OutputInterface::OUTPUT_RAW | OutputInterface::VERBOSITY_QUIET);
625

    
626
            if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
627
                $output->writeln('<comment>Exception trace:</comment>', OutputInterface::VERBOSITY_QUIET);
628

    
629
                // exception related properties
630
                $trace = $e->getTrace();
631
                array_unshift($trace, array(
632
                    'function' => '',
633
                    'file' => $e->getFile() !== null ? $e->getFile() : 'n/a',
634
                    'line' => $e->getLine() !== null ? $e->getLine() : 'n/a',
635
                    'args' => array(),
636
                ));
637

    
638
                for ($i = 0, $count = count($trace); $i < $count; ++$i) {
639
                    $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
640
                    $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
641
                    $function = $trace[$i]['function'];
642
                    $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a';
643
                    $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';
644

    
645
                    $output->writeln(sprintf(' %s%s%s() at <info>%s:%s</info>', $class, $type, $function, $file, $line), OutputInterface::VERBOSITY_QUIET);
646
                }
647

    
648
                $output->writeln('', OutputInterface::VERBOSITY_QUIET);
649
            }
650
        } while ($e = $e->getPrevious());
651

    
652
        if (null !== $this->runningCommand) {
653
            $output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())), OutputInterface::VERBOSITY_QUIET);
654
            $output->writeln('', OutputInterface::VERBOSITY_QUIET);
655
        }
656
    }
657

    
658
    /**
659
     * Tries to figure out the terminal width in which this application runs.
660
     *
661
     * @return int|null
662
     */
663
    protected function getTerminalWidth()
664
    {
665
        $dimensions = $this->getTerminalDimensions();
666

    
667
        return $dimensions[0];
668
    }
669

    
670
    /**
671
     * Tries to figure out the terminal height in which this application runs.
672
     *
673
     * @return int|null
674
     */
675
    protected function getTerminalHeight()
676
    {
677
        $dimensions = $this->getTerminalDimensions();
678

    
679
        return $dimensions[1];
680
    }
681

    
682
    /**
683
     * Tries to figure out the terminal dimensions based on the current environment.
684
     *
685
     * @return array Array containing width and height
686
     */
687
    public function getTerminalDimensions()
688
    {
689
        if ($this->terminalDimensions) {
690
            return $this->terminalDimensions;
691
        }
692

    
693
        if ('\\' === DIRECTORY_SEPARATOR) {
694
            // extract [w, H] from "wxh (WxH)"
695
            if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) {
696
                return array((int) $matches[1], (int) $matches[2]);
697
            }
698
            // extract [w, h] from "wxh"
699
            if (preg_match('/^(\d+)x(\d+)$/', $this->getConsoleMode(), $matches)) {
700
                return array((int) $matches[1], (int) $matches[2]);
701
            }
702
        }
703

    
704
        if ($sttyString = $this->getSttyColumns()) {
705
            // extract [w, h] from "rows h; columns w;"
706
            if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) {
707
                return array((int) $matches[2], (int) $matches[1]);
708
            }
709
            // extract [w, h] from "; h rows; w columns"
710
            if (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) {
711
                return array((int) $matches[2], (int) $matches[1]);
712
            }
713
        }
714

    
715
        return array(null, null);
716
    }
717

    
718
    /**
719
     * Sets terminal dimensions.
720
     *
721
     * Can be useful to force terminal dimensions for functional tests.
722
     *
723
     * @param int $width  The width
724
     * @param int $height The height
725
     *
726
     * @return Application The current application
727
     */
728
    public function setTerminalDimensions($width, $height)
729
    {
730
        $this->terminalDimensions = array($width, $height);
731

    
732
        return $this;
733
    }
734

    
735
    /**
736
     * Configures the input and output instances based on the user arguments and options.
737
     *
738
     * @param InputInterface  $input  An InputInterface instance
739
     * @param OutputInterface $output An OutputInterface instance
740
     */
741
    protected function configureIO(InputInterface $input, OutputInterface $output)
742
    {
743
        if (true === $input->hasParameterOption(array('--ansi'), true)) {
744
            $output->setDecorated(true);
745
        } elseif (true === $input->hasParameterOption(array('--no-ansi'), true)) {
746
            $output->setDecorated(false);
747
        }
748

    
749
        if (true === $input->hasParameterOption(array('--no-interaction', '-n'), true)) {
750
            $input->setInteractive(false);
751
        } elseif (function_exists('posix_isatty') && $this->getHelperSet()->has('question')) {
752
            $inputStream = $this->getHelperSet()->get('question')->getInputStream();
753
            if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) {
754
                $input->setInteractive(false);
755
            }
756
        }
757

    
758
        if (true === $input->hasParameterOption(array('--quiet', '-q'), true)) {
759
            $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
760
        } else {
761
            if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || $input->getParameterOption('--verbose', false, true) === 3) {
762
                $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
763
            } elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || $input->getParameterOption('--verbose', false, true) === 2) {
764
                $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
765
            } elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) {
766
                $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
767
            }
768
        }
769
    }
770

    
771
    /**
772
     * Runs the current command.
773
     *
774
     * If an event dispatcher has been attached to the application,
775
     * events are also dispatched during the life-cycle of the command.
776
     *
777
     * @param Command         $command A Command instance
778
     * @param InputInterface  $input   An Input instance
779
     * @param OutputInterface $output  An Output instance
780
     *
781
     * @return int 0 if everything went fine, or an error code
782
     *
783
     * @throws \Exception when the command being run threw an exception
784
     */
785
    protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
786
    {
787
        foreach ($command->getHelperSet() as $helper) {
788
            if ($helper instanceof InputAwareInterface) {
789
                $helper->setInput($input);
790
            }
791
        }
792

    
793
        if (null === $this->dispatcher) {
794
            return $command->run($input, $output);
795
        }
796

    
797
        // bind before the console.command event, so the listeners have access to input options/arguments
798
        try {
799
            $command->mergeApplicationDefinition();
800
            $input->bind($command->getDefinition());
801
        } catch (ExceptionInterface $e) {
802
            // ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition
803
        }
804

    
805
        $event = new ConsoleCommandEvent($command, $input, $output);
806
        $this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event);
807

    
808
        if ($event->commandShouldRun()) {
809
            try {
810
                $exitCode = $command->run($input, $output);
811
            } catch (\Exception $e) {
812
                $event = new ConsoleExceptionEvent($command, $input, $output, $e, $e->getCode());
813
                $this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event);
814

    
815
                $e = $event->getException();
816

    
817
                $event = new ConsoleTerminateEvent($command, $input, $output, $e->getCode());
818
                $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event);
819

    
820
                throw $e;
821
            }
822
        } else {
823
            $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED;
824
        }
825

    
826
        $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);
827
        $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event);
828

    
829
        return $event->getExitCode();
830
    }
831

    
832
    /**
833
     * Gets the name of the command based on input.
834
     *
835
     * @param InputInterface $input The input interface
836
     *
837
     * @return string The command name
838
     */
839
    protected function getCommandName(InputInterface $input)
840
    {
841
        return $input->getFirstArgument();
842
    }
843

    
844
    /**
845
     * Gets the default input definition.
846
     *
847
     * @return InputDefinition An InputDefinition instance
848
     */
849
    protected function getDefaultInputDefinition()
850
    {
851
        return new InputDefinition(array(
852
            new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
853

    
854
            new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'),
855
            new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
856
            new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'),
857
            new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'),
858
            new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'),
859
            new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'),
860
            new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
861
        ));
862
    }
863

    
864
    /**
865
     * Gets the default commands that should always be available.
866
     *
867
     * @return Command[] An array of default Command instances
868
     */
869
    protected function getDefaultCommands()
870
    {
871
        return array(new HelpCommand(), new ListCommand());
872
    }
873

    
874
    /**
875
     * Gets the default helper set with the helpers that should always be available.
876
     *
877
     * @return HelperSet A HelperSet instance
878
     */
879
    protected function getDefaultHelperSet()
880
    {
881
        return new HelperSet(array(
882
            new FormatterHelper(),
883
            new DebugFormatterHelper(),
884
            new ProcessHelper(),
885
            new QuestionHelper(),
886
        ));
887
    }
888

    
889
    /**
890
     * Runs and parses stty -a if it's available, suppressing any error output.
891
     *
892
     * @return string
893
     */
894
    private function getSttyColumns()
895
    {
896
        if (!function_exists('proc_open')) {
897
            return;
898
        }
899

    
900
        $descriptorspec = array(1 => array('pipe', 'w'), 2 => array('pipe', 'w'));
901
        $process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, array('suppress_errors' => true));
902
        if (is_resource($process)) {
903
            $info = stream_get_contents($pipes[1]);
904
            fclose($pipes[1]);
905
            fclose($pipes[2]);
906
            proc_close($process);
907

    
908
            return $info;
909
        }
910
    }
911

    
912
    /**
913
     * Runs and parses mode CON if it's available, suppressing any error output.
914
     *
915
     * @return string|null <width>x<height> or null if it could not be parsed
916
     */
917
    private function getConsoleMode()
918
    {
919
        if (!function_exists('proc_open')) {
920
            return;
921
        }
922

    
923
        $descriptorspec = array(1 => array('pipe', 'w'), 2 => array('pipe', 'w'));
924
        $process = proc_open('mode CON', $descriptorspec, $pipes, null, null, array('suppress_errors' => true));
925
        if (is_resource($process)) {
926
            $info = stream_get_contents($pipes[1]);
927
            fclose($pipes[1]);
928
            fclose($pipes[2]);
929
            proc_close($process);
930

    
931
            if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
932
                return $matches[2].'x'.$matches[1];
933
            }
934
        }
935
    }
936

    
937
    /**
938
     * Returns abbreviated suggestions in string format.
939
     *
940
     * @param array $abbrevs Abbreviated suggestions to convert
941
     *
942
     * @return string A formatted string of abbreviated suggestions
943
     */
944
    private function getAbbreviationSuggestions($abbrevs)
945
    {
946
        return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '');
947
    }
948

    
949
    /**
950
     * Returns the namespace part of the command name.
951
     *
952
     * This method is not part of public API and should not be used directly.
953
     *
954
     * @param string $name  The full name of the command
955
     * @param string $limit The maximum number of parts of the namespace
956
     *
957
     * @return string The namespace of the command
958
     */
959
    public function extractNamespace($name, $limit = null)
960
    {
961
        $parts = explode(':', $name);
962
        array_pop($parts);
963

    
964
        return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit));
965
    }
966

    
967
    /**
968
     * Finds alternative of $name among $collection,
969
     * if nothing is found in $collection, try in $abbrevs.
970
     *
971
     * @param string             $name       The string
972
     * @param array|\Traversable $collection The collection
973
     *
974
     * @return string[] A sorted array of similar string
975
     */
976
    private function findAlternatives($name, $collection)
977
    {
978
        $threshold = 1e3;
979
        $alternatives = array();
980

    
981
        $collectionParts = array();
982
        foreach ($collection as $item) {
983
            $collectionParts[$item] = explode(':', $item);
984
        }
985

    
986
        foreach (explode(':', $name) as $i => $subname) {
987
            foreach ($collectionParts as $collectionName => $parts) {
988
                $exists = isset($alternatives[$collectionName]);
989
                if (!isset($parts[$i]) && $exists) {
990
                    $alternatives[$collectionName] += $threshold;
991
                    continue;
992
                } elseif (!isset($parts[$i])) {
993
                    continue;
994
                }
995

    
996
                $lev = levenshtein($subname, $parts[$i]);
997
                if ($lev <= strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) {
998
                    $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
999
                } elseif ($exists) {
1000
                    $alternatives[$collectionName] += $threshold;
1001
                }
1002
            }
1003
        }
1004

    
1005
        foreach ($collection as $item) {
1006
            $lev = levenshtein($name, $item);
1007
            if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
1008
                $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
1009
            }
1010
        }
1011

    
1012
        $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; });
1013
        asort($alternatives);
1014

    
1015
        return array_keys($alternatives);
1016
    }
1017

    
1018
    /**
1019
     * Sets the default Command name.
1020
     *
1021
     * @param string $commandName The Command name
1022
     */
1023
    public function setDefaultCommand($commandName)
1024
    {
1025
        $this->defaultCommand = $commandName;
1026
    }
1027

    
1028
    private function stringWidth($string)
1029
    {
1030
        if (false === $encoding = mb_detect_encoding($string, null, true)) {
1031
            return strlen($string);
1032
        }
1033

    
1034
        return mb_strwidth($string, $encoding);
1035
    }
1036

    
1037
    private function splitStringByWidth($string, $width)
1038
    {
1039
        // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly.
1040
        // additionally, array_slice() is not enough as some character has doubled width.
1041
        // we need a function to split string not by character count but by string width
1042
        if (false === $encoding = mb_detect_encoding($string, null, true)) {
1043
            return str_split($string, $width);
1044
        }
1045

    
1046
        $utf8String = mb_convert_encoding($string, 'utf8', $encoding);
1047
        $lines = array();
1048
        $line = '';
1049
        foreach (preg_split('//u', $utf8String) as $char) {
1050
            // test if $char could be appended to current line
1051
            if (mb_strwidth($line.$char, 'utf8') <= $width) {
1052
                $line .= $char;
1053
                continue;
1054
            }
1055
            // if not, push current line to array and make new line
1056
            $lines[] = str_pad($line, $width);
1057
            $line = $char;
1058
        }
1059
        if ('' !== $line) {
1060
            $lines[] = count($lines) ? str_pad($line, $width) : $line;
1061
        }
1062

    
1063
        mb_convert_variables($encoding, 'utf8', $lines);
1064

    
1065
        return $lines;
1066
    }
1067

    
1068
    /**
1069
     * Returns all namespaces of the command name.
1070
     *
1071
     * @param string $name The full name of the command
1072
     *
1073
     * @return string[] The namespaces of the command
1074
     */
1075
    private function extractAllNamespaces($name)
1076
    {
1077
        // -1 as third argument is needed to skip the command short name when exploding
1078
        $parts = explode(':', $name, -1);
1079
        $namespaces = array();
1080

    
1081
        foreach ($parts as $part) {
1082
            if (count($namespaces)) {
1083
                $namespaces[] = end($namespaces).':'.$part;
1084
            } else {
1085
                $namespaces[] = $part;
1086
            }
1087
        }
1088

    
1089
        return $namespaces;
1090
    }
1091
}
(2-2/8)