1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace Symfony\Component\Console;
13:
14: use Symfony\Component\Console\Application;
15: use Symfony\Component\Console\Input\StringInput;
16: use Symfony\Component\Console\Output\ConsoleOutput;
17: use Symfony\Component\Process\ProcessBuilder;
18: use Symfony\Component\Process\PhpExecutableFinder;
19:
20: 21: 22: 23: 24: 25: 26: 27: 28:
29: class Shell
30: {
31: private $application;
32: private $history;
33: private $output;
34: private $hasReadline;
35: private $prompt;
36: private $processIsolation;
37:
38: 39: 40: 41: 42: 43: 44: 45:
46: public function __construct(Application $application)
47: {
48: $this->hasReadline = function_exists('readline');
49: $this->application = $application;
50: $this->history = getenv('HOME').'/.history_'.$application->getName();
51: $this->output = new ConsoleOutput();
52: $this->prompt = $application->getName().' > ';
53: $this->processIsolation = false;
54: }
55:
56: 57: 58:
59: public function run()
60: {
61: $this->application->setAutoExit(false);
62: $this->application->setCatchExceptions(true);
63:
64: if ($this->hasReadline) {
65: readline_read_history($this->history);
66: readline_completion_function(array($this, 'autocompleter'));
67: }
68:
69: $this->output->writeln($this->getHeader());
70: $php = null;
71: if ($this->processIsolation) {
72: $finder = new PhpExecutableFinder();
73: $php = $finder->find();
74: $this->output->writeln(<<<EOF
75: <info>Running with process isolation, you should consider this:</info>
76: * each command is executed as separate process,
77: * commands don't support interactivity, all params must be passed explicitly,
78: * commands output is not colorized.
79:
80: EOF
81: );
82: }
83:
84: while (true) {
85: $command = $this->readline();
86:
87: if (false === $command) {
88: $this->output->writeln("\n");
89:
90: break;
91: }
92:
93: if ($this->hasReadline) {
94: readline_add_history($command);
95: readline_write_history($this->history);
96: }
97:
98: if ($this->processIsolation) {
99: $pb = new ProcessBuilder();
100:
101: $process = $pb
102: ->add($php)
103: ->add($_SERVER['argv'][0])
104: ->add($command)
105: ->inheritEnvironmentVariables(true)
106: ->getProcess()
107: ;
108:
109: $output = $this->output;
110: $process->run(function($type, $data) use ($output) {
111: $output->writeln($data);
112: });
113:
114: $ret = $process->getExitCode();
115: } else {
116: $ret = $this->application->run(new StringInput($command), $this->output);
117: }
118:
119: if (0 !== $ret) {
120: $this->output->writeln(sprintf('<error>The command terminated with an error status (%s)</error>', $ret));
121: }
122: }
123: }
124:
125: /**
126: * Returns the shell header.
127: *
128: * @return string The header string
129: */
130: protected function getHeader()
131: {
132: return <<<EOF
133:
134: Welcome to the <info>{$this->application->getName()}</info> shell (<comment>{$this->application->getVersion()}</comment>).
135:
136: At the prompt, type <comment>help</comment> for some help,
137: or <comment>list</comment> to get a list of available commands.
138:
139: To exit the shell, type <comment>^D</comment>.
140:
141: EOF;
142: }
143:
144: 145: 146: 147: 148: 149: 150:
151: private function autocompleter($text)
152: {
153: $info = readline_info();
154: $text = substr($info['line_buffer'], 0, $info['end']);
155:
156: if ($info['point'] !== $info['end']) {
157: return true;
158: }
159:
160:
161: if (false === strpos($text, ' ') || !$text) {
162: return array_keys($this->application->all());
163: }
164:
165:
166: try {
167: $command = $this->application->find(substr($text, 0, strpos($text, ' ')));
168: } catch (\Exception $e) {
169: return true;
170: }
171:
172: $list = array('--help');
173: foreach ($command->getDefinition()->getOptions() as $option) {
174: $list[] = '--'.$option->getName();
175: }
176:
177: return $list;
178: }
179:
180: 181: 182: 183: 184:
185: private function readline()
186: {
187: if ($this->hasReadline) {
188: $line = readline($this->prompt);
189: } else {
190: $this->output->write($this->prompt);
191: $line = fgets(STDIN, 1024);
192: $line = (!$line && strlen($line) == 0) ? false : rtrim($line);
193: }
194:
195: return $line;
196: }
197:
198: public function getProcessIsolation()
199: {
200: return $this->processIsolation;
201: }
202:
203: public function setProcessIsolation($processIsolation)
204: {
205: $this->processIsolation = (Boolean) $processIsolation;
206:
207: if ($this->processIsolation && !class_exists('Symfony\\Component\\Process\\Process')) {
208: throw new \RuntimeException('Unable to isolate processes as the Symfony Process Component is not installed.');
209: }
210: }
211: }
212: