From bdacff3c359232769e55dde7d84bfa0bee3cd33b Mon Sep 17 00:00:00 2001 From: Pavel Shevaev Date: Mon, 16 May 2022 14:14:49 +0300 Subject: [PATCH] first commit --- README.md | 0 taskman.inc.php | 894 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 894 insertions(+) create mode 100644 README.md create mode 100644 taskman.inc.php diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/taskman.inc.php b/taskman.inc.php new file mode 100644 index 0000000..58a1669 --- /dev/null +++ b/taskman.inc.php @@ -0,0 +1,894 @@ +file = $refl->getFileName(); + $this->line = $refl->getStartLine(); + + if(is_string($func) && $name === null && $props === null) + { + $this->name = self::extractName($func); + $this->_parseProps($refl); + } + else + { + $this->name = $name; + $this->props = $props; + } + + $this->func = $func; + } + + function validate() + { + try + { + foreach($this->_getBeforeDeps() as $dep_task) + taskman_gettask($dep_task); + + foreach($this->_getDeps() as $dep_task) + taskman_gettask($dep_task); + + foreach($this->_getAfterDeps() as $dep_task) + taskman_gettask($dep_task); + } + catch(Exception $e) + { + throw new Exception("Task '{$this->name}' validation error: " . $e->getMessage()); + } + } + + function getName() + { + return $this->name; + } + + function getFile() + { + return $this->file; + } + + function getLine() + { + return $this->line; + } + + function getFunc() + { + return $this->func; + } + + function getArgs() + { + return $this->args; + } + + function getAliases() + { + $alias = $this->getPropOr("alias", ""); + if(is_array($alias)) + return $alias; + else if($alias && is_string($alias)) + return explode(",", $alias); + else + return array(); + } + + function run($args = array()) + { + global $TASKMAN_CURRENT_TASK; + global $TASKMAN_STACK; + global $TASKMAN_NO_DEPS; + global $TASKMAN_START_TIME; + + $args_str = serialize($args); + + if((isset($this->has_run[$args_str]) && $this->has_run[$args_str]) || + $this->is_running) + return; + + $this->is_running = true; + $this->args = $args; + + try + { + if(!$TASKMAN_NO_DEPS) + { + taskman_runtasks($this->_getBeforeDeps()); + taskman_runtasks($this->_getDeps()); + } + + taskman_sysmsg("************************ Running task '" . $this->getName() . "' ************************\n"); + + $bench = microtime(true); + + $TASKMAN_CURRENT_TASK = $this; + $TASKMAN_STACK[] = $this; + call_user_func_array($this->func, array($this->args)); + array_pop($TASKMAN_STACK); + + if(!$TASKMAN_NO_DEPS) + taskman_runtasks($this->_getAfterDeps()); + + taskman_sysmsg("************************* '" . $this->getName() . "' done (" . + round(microtime(true)-$bench,2) . '/' .round(microtime(true)-$TASKMAN_START_TIME,2) . " sec.)*************************\n"); + + $this->has_run[$args_str] = true; + $this->is_running = false; + } + catch(Exception $e) + { + $error_handler = $GLOBALS['TASKMAN_ERROR_HANDLER']; + if($error_handler) + $error_handler($e); + else + throw $e; + } + } + + private function _getBeforeDeps() + { + return $this->_collectRelatedTasks("before"); + } + + private function _getAfterDeps() + { + return $this->_collectRelatedTasks("after"); + } + + private function _collectRelatedTasks($prop_name) + { + $arr = array(); + foreach(taskman_gettasks() as $task_obj) + { + if($this->getName() == $task_obj->getName()) + continue; + + $value = $task_obj->getPropOr($prop_name, ""); + if($value == $this->getName() || in_array($value, $this->getAliases())) + $arr[] = $task_obj; + } + return $arr; + } + + private function _getDeps() + { + $deps = $this->getPropOr('deps', ""); + if(is_array($deps)) + return $deps; + else if($deps && is_string($deps)) + return _taskman_parse_taskstr($deps); + return array(); + } + + private function _parseProps(ReflectionFunction $refl) + { + if(preg_match_all('~@(\S+)([^\n]+)?\n~', $refl->getDocComment(), $matches)) + { + foreach($matches[1] as $idx => $match) + $this->props[$match] = trim($matches[2][$idx]); + } + } + + static function extractName($func) + { + if(strpos($func, "task_") === 0) + return substr($func, strlen('task_'), strlen($func)); + else if(strpos($func, 'taskman\task_') === 0) + return substr($func, strlen('taskman\task_'), strlen($func)); + } + + function getPropOr($name, $def) + { + return isset($this->props[$name]) ? $this->props[$name] : $def; + } + + function getProp($name) + { + return $this->getPropOr($name, null); + } + + function hasProp($name) + { + return isset($this->props[$name]); + } + + function getProps() + { + return $this->props; + } +} + +function taskman_reset() +{ + $GLOBALS['TASKMAN_TASKS'] = array(); + $GLOBALS['TASKMAN_TASK_ALIASES'] = array(); + $GLOBALS['TASKMAN_CURRENT_TASK'] = null; + $GLOBALS['TASKMAN_CONFIG'] = array(); + + _taskman_collecttasks(); +} + +function taskman_str($str) +{ + if(strpos($str, '%') === false) + return $str; + + $str = preg_replace_callback( + '~%\(([^\)]+)\)%~', + function($m) { return taskman_prop($m[1]); }, + $str + ); + return $str; +} + +function taskman_run($argv = array(), $help_func = null, $proc_argv = true) +{ + $GLOBALS['TASKMAN_START_TIME'] = microtime(true); + + if($help_func) + $GLOBALS['TASKMAN_HELP_FUNC'] = $help_func; + if($proc_argv) + taskman_process_argv($argv); + $GLOBALS['TASKMAN_SCRIPT'] = array_shift($argv); + + _taskman_collecttasks(); + + $always_tasks = array(); + $default_task = null; + foreach(taskman_gettasks() as $task_obj) + { + if($task_obj->hasProp('always')) + $always_tasks[] = $task_obj; + if($task_obj->hasProp('default')) + { + if($default_task) + throw new TaskmanException("Assigned default task '" . $default_task->getName() . "' conflicts with '" . $task_obj->getName() . "'"); + else + $default_task = $task_obj; + } + } + + foreach($always_tasks as $always_task) + $always_task->run(array()); + + if(sizeof($argv) > 0) + { + $task_str = array_shift($argv); + $tasks = _taskman_parse_taskstr($task_str); + + if(count($tasks) == 1 && !taskman_isset_task($tasks[0])) + { + $pattern = $tasks[0]; + if($pattern[0] == '~') + { + $pattern = substr($pattern, 1, strlen($pattern) - 1); + $is_similar = true; + } + elseif(substr($pattern, -1, 1) == '~') + { + $pattern = substr($pattern, 0, strlen($pattern) - 1); + $is_similar = true; + } + else + $is_similar = false; + $hints = taskman_get_hints($pattern); + + if($is_similar && count($hints) == 1) + $tasks = $hints; + else + { + printf("ERROR! Task %s not found\n", $tasks[0]); + if($hints) + { + printf("Similar tasks:\n"); + foreach($hints as $hint) + printf(" %s\n", $hint); + } + exit(1); + } + } + + taskman_runtasks($tasks, $argv); + } + else if($default_task) + $default_task->run($argv); + + taskman_sysmsg("************************ All done (".round(microtime(true)-$GLOBALS['TASKMAN_START_TIME'],2)." sec.)************************\n"); +} + +function taskman_isset_task($task) +{ + global $TASKMAN_TASKS; + global $TASKMAN_TASK_ALIASES; + return isset($TASKMAN_TASKS[$task]) || isset($TASKMAN_TASK_ALIASES[$task]); +} + +function taskman_get_hints($task) +{ + global $TASKMAN_TASKS; + global $TASKMAN_TASK_ALIASES; + $tasks = array_merge(array_keys($TASKMAN_TASKS), array_keys($TASKMAN_TASK_ALIASES)); + $found = array_filter($tasks, function($v) use($task) { return strpos($v, $task) === 0; }); + $found = array_merge($found, array_filter($tasks, function($v) use($task) { $pos = strpos($v, $task); return $pos !== false && $pos > 0; })); + return $found; +} + +function taskman_process_argv(&$argv) +{ + global $TASKMAN_LOG_LEVEL; + global $TASKMAN_BATCH; + global $TASKMAN_NO_DEPS; + + $filtered = array(); + $process_defs = false; + for($i=0;$i $args) + { + if(isset($TASKMAN_TASKS[$name])) + throw new TaskmanException("Task '$name' is already defined"); + + $task = null; + if(is_string($args)) + { + $task = new TaskmanTask($args); + } + else if(is_array($args)) + { + $props = array(); + if(sizeof($args) > 2) + { + $props = $args[1]; + $func = $args[2]; + } + else + $func = $args[1]; + + $task = new TaskmanTask($func, $name, $props); + } + else + throw new Exception("Task '$name' is invalid"); + + $TASKMAN_TASKS[$name] = $task; + + foreach($task->getAliases() as $alias) + { + if(isset($TASKMAN_TASKS[$alias]) || isset($TASKMAN_TASK_ALIASES[$alias])) + throw new TaskmanException("Alias '$alias' is already defined for task '$name'"); + $TASKMAN_TASK_ALIASES[$alias] = $task; + } + } + + foreach($TASKMAN_TASKS as $task) + $task->validate(); +} + +function _taskman_get_task_candidates() +{ + global $TASKMAN_CLOSURES; + + $cands = array(); + + //1. get tasks defined as regular functions + $funcs = get_defined_functions(); + foreach($funcs['user'] as $func) + { + $name = TaskmanTask::extractName($func); + if(!$name) + continue; + $cands[$name] = $func; + } + + //2. get tasks defined as closures + foreach($TASKMAN_CLOSURES as $name => $args) + { + if(isset($cands[$name])) + throw new Exception("Task '$name' is already defined"); + $cands[$name] = $args; + } + + ksort($cands); + + return $cands; +} + +function taskman_gettasks() +{ + global $TASKMAN_TASKS; + return $TASKMAN_TASKS; +} + +function taskman_gettask($task) +{ + global $TASKMAN_TASKS; + global $TASKMAN_TASK_ALIASES; + + if(!is_scalar($task)) + throw new TaskmanException("Bad task name"); + + if(isset($TASKMAN_TASKS[$task])) + return $TASKMAN_TASKS[$task]; + + if(isset($TASKMAN_TASK_ALIASES[$task])) + return $TASKMAN_TASK_ALIASES[$task]; + + throw new TaskmanException("Task with name/alias '{$task}' does not exist"); +} + +function _taskman_parse_taskstr($str) +{ + $task_spec = array(); + $items = explode(',', $str); + foreach($items as $item) + { + $args = null; + $task = $item; + if(strpos($item, ' ') !== false) + @list($task, $args) = explode(' ', $item, 2); + + if($args) + $task_spec[] = array($task, explode(' ', $args)); + else + $task_spec[] = $task; + } + return $task_spec; +} + +function taskman_runtask($task, $args = array()) +{ + if($task instanceof TaskmanTask) + $task_obj = $task; + else + $task_obj = taskman_gettask($task); + + $task_obj->run($args); +} + +function taskman_runtasks($tasks, $args = array()) +{ + foreach($tasks as $task_spec) + { + if(is_array($task_spec)) + taskman_runtask($task_spec[0], $task_spec[1]); + else + taskman_runtask($task_spec, $args); + } +} + +function taskman_current_task() +{ + global $TASKMAN_CURRENT_TASK; + return $TASKMAN_CURRENT_TASK; +} + +function taskman_shell_get($cmd, $as_string = true) +{ + exec($cmd, $out, $code); + if($code !== 0) + throw new Exception("Error($code) executing shell cmd '$cmd'"); + return $as_string ? implode("", $out) : $out; +} + +function taskman_shell_ensure($cmd, &$out=null) +{ + taskman_shell($cmd, $ret, $out); + if($ret != 0) + throw new TaskmanException("Shell execution error(exit code $ret)"); +} + +function taskman_shell($cmd, &$ret=null, &$out=null) +{ + taskman_msg(" shell: $cmd\n"); + taskman_msg(" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"); + + if(func_num_args() < 3) + system($cmd, $ret); + else + _taskman_execute_proc_cmd($cmd, $ret, $out); +} + +function _taskman_execute_proc_cmd($cmd, &$ret, &$out) +{ + //TODO: do we really need to redirect error stream? + $proc = popen("$cmd 2>&1", 'r'); + + $log = ''; + //TODO: how can this be? + if(is_string($proc)) + { + $log = $proc; + taskman_log($log, 1); + } + else + { + while($logline = fgets($proc)) + { + $log .= $logline; + taskman_log($logline, 1); + } + } + $out = explode("\n", $log); + $ret = pclose($proc); +} + +function taskman_dmsg($msg) +{ + taskman_log($msg, 2); +} + +function taskman_msg($msg) +{ + taskman_log($msg, 1); +} + +function taskman_sysmsg($msg) +{ + taskman_log($msg, 0); +} + +function taskman_log($msg, $level = 1) +{ + global $TASKMAN_LOG_LEVEL; + + if($TASKMAN_LOG_LEVEL < $level) + return; + + $logger = $GLOBALS['TASKMAN_LOGGER']; + call_user_func_array($logger, array($msg)); +} + +function _taskman_default_logger($msg) +{ + echo $msg; +} + +function _taskman_resolve_callable_prop($name) +{ + $prop = $GLOBALS['TASKMAN_PROP_' . $name]; + if(!($prop instanceof \Closure)) + return; + $value = $prop(); + $GLOBALS['TASKMAN_PROP_' . $name] = $value; +} + +function taskman_prop($name) +{ + if(!isset($GLOBALS['TASKMAN_PROP_' . $name])) + throw new TaskmanException("Property '$name' is not set"); + _taskman_resolve_callable_prop($name); + return $GLOBALS['TASKMAN_PROP_' . $name]; +} + +function taskman_propor($name, $def) +{ + if(!isset($GLOBALS['TASKMAN_PROP_' . $name])) + return $def; + _taskman_resolve_callable_prop($name); + return $GLOBALS['TASKMAN_PROP_' . $name]; +} + +function taskman_propset($name, $value) +{ + $GLOBALS['TASKMAN_PROP_' . $name] = $value; +} + +function taskman_propadd($name, $value) +{ + if(!isset($GLOBALS['TASKMAN_PROP_' . $name])) + $GLOBALS['TASKMAN_PROP_' . $name] = ''; + _taskman_resolve_callable_prop($name); + $GLOBALS['TASKMAN_PROP_' . $name] .= $value; +} + +function taskman_propsetor($name, $value) +{ + if(!isset($GLOBALS['TASKMAN_PROP_' . $name])) + $GLOBALS['TASKMAN_PROP_' . $name] = $value; +} + +function taskman_propunset($name) +{ + unset($GLOBALS['TASKMAN_PROP_' . $name]); +} + +function taskman_getprops() +{ + $props = array(); + foreach($GLOBALS as $key => $value) + { + if(($idx = strpos($key, 'TASKMAN_PROP_')) === 0) + { + $name = substr($key, strlen('TASKMAN_PROP_')); + $props[$name] = taskman_prop($name); + } + } + return $props; +} + +function taskman_isprop($name) +{ + return isset($GLOBALS['TASKMAN_PROP_' . $name]); +} + +function taskman_default_usage($script_name = "") +{ + echo "\nUsage:\n $script_name [OPTIONS] [,,..] [-D PROP1=value [-D PROP2]]\n\n"; + echo "Available options:\n"; + echo " -c specify PHP script to be included (handy for setting props,config options,etc)\n"; + echo " -V be super verbose\n"; + echo " -q be quite, only system messages\n"; + echo " -b batch mode: be super quite, don't even output any system messages\n"; + echo " -- pass all options verbatim after this mark\n"; +} + +/** + * @desc Shows this help + */ +function task_help($args = array()) +{ + $filter = ''; + if(isset($args[0])) + $filter = $args[0]; + + $maxlen = -1; + $tasks = array(); + $all = taskman_gettasks(); + foreach($all as $task) + { + if($filter && (strpos($task->getName(), $filter) === false && strpos($task->getPropOr("alias", ""), $filter) === false)) + continue; + + if(strlen($task->getName()) > $maxlen) + $maxlen = strlen($task->getName()); + + $tasks[] = $task; + } + + if(!$args) + { + $help_func = $GLOBALS['TASKMAN_HELP_FUNC']; + $help_func(); + echo "\n"; + } + + echo "Available tasks:\n"; + foreach($tasks as $task) + { + $props_string = ''; + $pad = $maxlen - strlen($task->getName()); + foreach($task->getProps() as $name => $value) + { + $props_string .= str_repeat(" ", $pad) .' @' . $name . ' ' . (is_string($value) ? $value : json_encode($value)) . "\n"; + $pad = $maxlen + 1; + } + $props_string = rtrim($props_string); + + echo "---------------------------------\n"; + $file = $task->getFile(); + $line = $task->getLine(); + echo " " . $task->getName() . $props_string . " ($file@$line)\n"; + } + echo "\n"; +} + + +/** + * @desc Encodes tasks as json array + */ +function task_json() +{ + $json = array(); + foreach(taskman_gettasks() as $task) + { + $json[$task->getName()] = $task->getProps(); + } + echo json_encode($json); +} + +} //namespace global + +namespace taskman { + +function get($name) +{ + return \taskman_prop($name); +} + +function getor($name, $def) +{ + return \taskman_propor($name, $def); +} + +function set($name, $value) +{ + \taskman_propset($name, $value); +} + +function setor($name, $value) +{ + \taskman_propsetor($name, $value); +} + +function add($name, $value) +{ + \taskman_propadd($name, $value); +} + +function is($name) +{ + return \taskman_isprop($name); +} + +function del($name) +{ + \taskman_propunset($name); +} + +function props() +{ + return \taskman_getprops(); +} + +function task($name) +{ + global $TASKMAN_CLOSURES; + + $args = func_get_args(); + $TASKMAN_CLOSURES[$name] = $args; +} + +function run($task, array $args = array()) +{ + \taskman_runtask($task, $args); +} + +function msg_dbg($msg) +{ + \taskman_dmsg($msg); +} + +function msg($msg) +{ + \taskman_msg($msg); +} + +function msg_sys($msg) +{ + \taskman_sysmsg($msg); +} + +function shell($cmd, &$out=null) +{ + \taskman_shell_ensure($cmd, $out); +} + +function shell_try($cmd, &$ret=null, &$out=null) +{ + \taskman_shell($cmd, $ret, $out); +} + +function _($str) +{ + return \taskman_str($str); +} + +function main($argv = array(), $help_func = null, $proc_argv = true) +{ + \taskman_run($argv, $help_func, $proc_argv); +} + +function usage($script_name = "") +{ + \taskman_default_usage($script_name); +} + +} //namespace taskman +//}}} +