--- /dev/null
+<?php
+
+namespace atick;
+
+class Proc implements Able
+{
+ /**
+ * Command string
+ * @var string
+ */
+ protected $command;
+
+ /**
+ * Process handle
+ * @var resource
+ */
+ protected $proc;
+
+ /**
+ * Proc's pipes
+ * @var array
+ */
+ protected $pipes;
+
+ protected $read;
+ protected $error;
+
+ /**
+ * @param string $command
+ * @param string $cwd
+ * @param array $env
+ * @throws \RuntimeException
+ */
+ function __construct($command, $cwd = null, array $env = null) {
+ $this->command = $command;
+ $this->proc = proc_open($command, [["pipe","r"],["pipe","w"],["pipe","w"]], $this->pipes, $cwd, $env);
+
+ if (!is_resource($this->proc) || !($status = proc_get_status($this->proc))) {
+ throw new \RuntimeException("Could not open proc '$command': " . error_get_last()["message"]);
+ }
+
+ stream_set_blocking($this->pipes[1], false);
+ stream_set_blocking($this->pipes[2], false);
+ }
+
+ /**
+ * Returns the command string
+ * @return string
+ */
+ function __toString() {
+ return (string) $this->command;
+ }
+
+ /**
+ * Cleanup pipes and proc handle
+ */
+ function __destruct() {
+ $this->close();
+ }
+
+ /**
+ * @inheritdoc
+ * @implements \aticker\Able
+ * @param \atick\Ticker $ticker
+ * @param callable $verify
+ */
+ function with(Ticker $ticker, callable $verify = null) {
+ if (is_callable($this->read)) {
+ $ticker->read($this->pipes[1], $this->read, $verify ?: array($this, "stat"));
+ } elseif (is_resource($this->read)) {
+ $ticker->read($this->pipes[1], function($fd) {
+ if (strlen($data = fread($fd, 8192))) {
+ fwrite($this->read, $data);
+ }
+ }, $verify ?: array($this, "stat"));
+ } else {
+ $ticker->read($this->pipes[1], function($fd) {
+ /* nirvana */
+ fread($fd, 8192);
+ }, $verify ?: array($this, "stat"));
+ }
+
+ if (is_callable($this->error)) {
+ $ticker->read($this->pipes[2], $this->error, $verify ?: array($this, "stat"));
+ } elseif (is_resource($this->error)) {
+ $ticker->read($this->pipes[2], function($fd) {
+ if (strlen($data = fread($fd, 8192))) {
+ fwrite($this->error, $data);
+ }
+ }, $verify ?: array($this, "stat"));
+ } else {
+ $ticker->read($this->pipes[2], function($fd) {
+ /* nirvana */
+ fread($fd, 8192);
+ }, $verify ?: array($this, "stat"));
+ }
+ }
+
+ function stat() {
+ echo "STAT $this\n";
+ if ($this->proc && proc_get_status($this->proc)["running"]) {
+ if ((isset($this->pipes[1]) && is_resource($this->pipes[1]) && !feof($this->pipes[1]))
+ && (isset($this->pipes[2]) && is_resource($this->pipes[2]) && !feof($this->pipes[2]))
+ ) {
+ if (isset($this->pipes[0]) && is_resource($this->pipes[0]) && !feof($this->pipes[0])) {
+ return self::READABLE | self::WRITABLE;
+ }
+ return self::READABLE;
+ }
+ }
+ $this->close();
+ return self::CLOSED;
+ }
+
+ function close($what = self::CLOSED) {
+ echo "PROC KILL $this $what\n";
+
+ if (!$what || ($what & self::WRITABLE)) {
+ if (is_resource($this->pipes[0])) {
+ fclose($this->pipes[0]);
+ }
+ $this->pipes[0] = null;
+ }
+
+ if (!$what || ($what & self::READABLE)) {
+ if (is_resource($this->pipes[1])) {
+ fclose($this->pipes[1]);
+ }
+ $this->pipes[1] = null;
+ if (is_resource($this->read)) {
+ fclose($this->read);
+ }
+ if (is_resource($this->pipes[2])) {
+ fclose($this->pipes[2]);
+ }
+ $this->pipes[2] = null;
+ if (is_resource($this->error)) {
+ fclose($this->error);
+ }
+ }
+
+ if (!$what && is_resource($this->proc)) {
+ proc_close($this->proc);
+ $this->proc = null;
+ }
+ }
+
+ /**
+ * @inheritdoc
+ * @implements \aticker\Able
+ * @param string $data
+ * @return int
+ */
+ function write($data) {
+ return fwrite($this->pipes[0], $data);
+ }
+
+ /**
+ * Where to read STDOUT into
+ * @param resource|callable $into
+ * @return \atick\Proc
+ * @throws \InvalidArgumentException
+ */
+ function read($into) {
+ if (is_resource($into) || is_callable($into)) {
+ $this->read = $into;
+ } else {
+ throw new \InvalidArgumentException("Not a valid resource or callback");
+ }
+ return $this;
+ }
+
+ /**
+ * Where to pass STDERR into
+ * @param resource|callable $into
+ * @return \atick\Proc
+ * @throws \InvalidArgumentException
+ */
+ function error($into) {
+ if (is_resource($into) || is_callable($into)) {
+ $this->error = $into;
+ } else {
+ throw new \InvalidArgumentException("Not a valid resource or callback");
+ }
+ return $this;
+ }
+}