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: private function autocompleter($text)
151: {
152: $info = readline_info();
153: $text = substr($info['line_buffer'], 0, $info['end']);
154:
155: if ($info['point'] !== $info['end']) {
156: return true;
157: }
158:
159:
160: if (false === strpos($text, ' ') || !$text) {
161: return array_keys($this->application->all());
162: }
163:
164:
165: try {
166: $command = $this->application->find(substr($text, 0, strpos($text, ' ')));
167: } catch (\Exception $e) {
168: return true;
169: }
170:
171: $list = array('--help');
172: foreach ($command->getDefinition()->getOptions() as $option) {
173: $list[] = '--'.$option->getName();
174: }
175:
176: return $list;
177: }
178:
179: 180: 181: 182: 183:
184: private function readline()
185: {
186: if ($this->hasReadline) {
187: $line = readline($this->prompt);
188: } else {
189: $this->output->write($this->prompt);
190: $line = fgets(STDIN, 1024);
191: $line = (!$line && strlen($line) == 0) ? false : rtrim($line);
192: }
193:
194: return $line;
195: }
196:
197: public function getProcessIsolation()
198: {
199: return $this->processIsolation;
200: }
201:
202: public function setProcessIsolation($processIsolation)
203: {
204: $this->processIsolation = (Boolean) $processIsolation;
205: }
206: }
207: