push  latest
PHP shell
LineReader.php
1 <?php
2 
3 namespace fpoirotte\push;
4 
5 class LineReader
6 {
7  protected $manager;
8  protected $stop;
9 
10  const PS1 = "\001\033[1;32m\002>>>\001\033[0m\002 ";
11 
12  public function __construct(\fpoirotte\push\Manager $manager)
13  {
14  $this->manager = $manager;
15  $this->stop = false;
16  $this->prompt = static::PS1;
17  }
18 
19  protected function filter($data)
20  {
21  if (substr($data, -1) === "\n") {
22  $data = (string) substr($data, 0, -1);
23  }
24 
25  $output = array();
26  $lines = explode("\n", $data);
27  $pattern = $this->manager->getEvalLocation();
28  $pattern = " in $pattern : eval()'d code on line ";
29  $pLen = strlen($pattern);
30  foreach ($lines as $line) {
31  $pos = strpos($line, $pattern);
32  if ($pos !== false) {
33  $matchEnd = strspn(substr($line, $pos + $pLen), '1234567890');
34  if ($pos + $pLen + $matchEnd === strlen($line)) {
35  $output[] = (string) substr($line, 0, $pos);
36  } else {
37  $output[] = $line;
38  }
39  } else {
40  $output[] = $line;
41  }
42  }
43  return implode("\n", $output) . "\n";
44  }
45 
46  protected function signalHandler($signo)
47  {
48  switch ($signo) {
49  case SIGINT:
50  // This is silly, but required to provide feedback.
51  echo "^C\n";
52  if (!$this->manager->isWorking()) {
53  return;
54  }
55  break;
56  }
57 
58  $this->manager->sendSignal($signo);
59  }
60 
61  protected function lineHandler($line)
62  {
63  if ($line === "") {
64  return;
65  }
66 
67  readline_callback_handler_remove();
68 
69  if ($line === null || $line == "exit") {
70  if ($line === null) {
71  echo "^D\n";
72  }
73 
74  // Explicit call to "exit" or Ctrl+D
75  $this->stop = true;
76  return;
77  }
78 
79  readline_add_history($line);
80  $this->manager->sendCommands($line);
81  $this->prompt = '';
82  }
83 
84  public function run()
85  {
86  declare(ticks=1);
87 
88  $stdout = $stderr = $control = null;
89  $this->manager->prepare($stdout, $stderr, $control);
90 
91  $signals = array(
92  'SIGHUP',
93  'SIGINT',
94  'SIGQUIT',
95  'SIGILL',
96  'SIGTRAP',
97  'SIGABRT',
98  'SIGIOT',
99  'SIGBUS',
100  'SIGFPE',
101  //'SIGKILL', // SIGKILL cannot be intercepted.
102  'SIGUSR1',
103  'SIGSEGV',
104  'SIGUSR2',
105  'SIGPIPE',
106  'SIGALRM',
107  'SIGTERM',
108  'SIGSTKFLT',
109  'SIGCLD',
110  'SIGCHLD',
111  //'SIGCONT', // Signals that suspend/resume execution
112  //'SIGSTOP', // are left intact. We want the manager
113  //'SIGTSTP', // to suspend its execution, not its children.
114  'SIGTTIN',
115  'SIGTTOU',
116  'SIGURG',
117  'SIGXCPU',
118  'SIGXFSZ',
119  'SIGVTALRM',
120  'SIGPROF',
121  'SIGWINCH',
122  'SIGPOLL',
123  'SIGIO',
124  'SIGPWR',
125  'SIGSYS',
126  'SIGBABY',
127  );
128 
129  foreach ($signals as $signal) {
130  if (!defined($signal)) {
131  continue;
132  }
133 
134  if (!pcntl_signal(constant($signal), array($this, 'signalHandler'), true)) {
135  throw new \RuntimeException('Unable to set up signal handler for ' . $signal);
136  }
137  }
138 
139  while (true) {
140  if ($this->stop) {
141  readline_write_history();
142  echo "Exiting...\n";
143  break;
144  }
145 
146  $r = array(STDIN, $stdout, $stderr, $control);
147  $w = $e = array();
148 
149  $nb = @stream_select($r, $w, $e, null);
150  pcntl_signal_dispatch();
151 
152  if ($nb === false) {
153  continue;
154  }
155 
156  if (in_array(STDIN, $r)) {
157  if ($this->manager->isWorking()) {
158  $tmp = fgetc(STDIN);
159  } else {
160  readline_callback_read_char();
161  }
162  }
163 
164  if (in_array($stdout, $r)) {
165  $read = fread($stdout, 8192);
166  if ($read !== false) {
167  fwrite(STDOUT, $this->filter($read));
168  }
169  }
170 
171  if (in_array($stderr, $r)) {
172  $read = fread($stderr, 8192);
173  if ($read !== false) {
174  fwrite(STDERR, $this->filter($read));
175  }
176  }
177 
178  if (in_array($control, $r)) {
179  try {
180  $this->manager->runOnce();
181  } catch (\RuntimeException $e) {
182  throw $e;
183  break;
184  }
185 
186  if (!$this->manager->isWorking()) {
187  $this->prompt = static::PS1;
188  }
189 
190  if (!readline_callback_handler_install($this->prompt, array($this, 'lineHandler'))) {
191  throw new \RuntimeException();
192  }
193  }
194  }
195  }
196 }