directory = $directory; } public function defineScript(string $name, string|array $script) { $this->scripts[$name] = $script; } public function evaluateDefinedScript(string $name) { $script = $this->scripts[$name]; $this->evaluate($script); } public function evaluate(string|array $script) { if (is_array($script)) { foreach ($script as $step) { $this->evaluate($step); } return; } $script = $this->expandString($script); // Determine what to do if (str_starts_with($script, '@')) { // starts with @, call on a defined script $subname = substr($script, 1); $subscript = $this->scripts[$subname]; $this->evaluate($subscript); } else { if (posix_isatty(STDOUT)) { printf("\e[0;33m> \e[0;93m%s\e[0m\n", $script); } else { printf("> %s\n", $script); } if (str_contains($script, ' ')) { [$script, $args] = explode(" ", $script, 2); $args = str_getcsv($args, ' ', "'"); } else { $args = []; } if (is_callable($script)) { // call script call_user_func($script, ...$args); } elseif (file_exists((string)$script) && fnmatch("*.php", (string)$script)) { include $script; } else { // call shell $cmdl = trim(escapeshellcmd((string)$script) . " " . join(" ", array_map("escapeshellarg", $args))); $proc = proc_open($cmdl, [ 0 => STDIN, 1 => STDOUT, 2 => STDERR ], $pipes, $this->directory); while ($stat = proc_get_status($proc)) { if ($stat['running'] === false) { $ec = $stat['exitcode']; if ($ec != 0) { printf("\e[31mcommand exited with code %d.\e[0m\n", $stat['exitcode']); throw new \RuntimeException("Command {$cmdl} exited with code {$ec}"); } break; } usleep(100000); } } } } public function expandString(string $input) { return preg_replace_callback('/(\$\{(.+?)\})/', function ($match) { return ($_ENV[$match[2]]??getenv($match[2]))??null; }, $input); } }