") { 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"; } } //namespace global namespace taskman { use Exception; class TaskmanException extends Exception {} class TaskmanTask { private $func; private $name; private $file; private $line; private $props = array(); private $is_running = false; private $has_run = array(); private $args = array(); function __construct(\Closure $func, $name, $props = array()) { $refl = new \ReflectionFunction($func); $this->file = $refl->getFileName(); $this->line = $refl->getStartLine(); $this->func = $func; $this->name = $name; $this->props = $props; } function validate() { try { foreach($this->_getBeforeDeps() as $dep_task) get_task($dep_task); foreach($this->_getDeps() as $dep_task) get_task($dep_task); foreach($this->_getAfterDeps() as $dep_task) get_task($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; $task_result = null; try { $TASKMAN_STACK[] = $this; $level = count($TASKMAN_STACK)-1; msg_sys("***** ".str_repeat('-', $level)."task '" . $this->getName() . "' start *****\n"); if(!$TASKMAN_NO_DEPS) { run_many($this->_getBeforeDeps()); run_many($this->_getDeps()); } $TASKMAN_CURRENT_TASK = $this; $bench = microtime(true); $task_result = call_user_func_array($this->func, array($this->args)); array_pop($TASKMAN_STACK); if(!$TASKMAN_NO_DEPS) run_many($this->_getAfterDeps()); msg_sys("***** ".str_repeat('-', $level)."task '" . $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; } return $task_result; } private function _getBeforeDeps() { return $this->_collectRelatedTasks("before"); } private function _getAfterDeps() { return $this->_collectRelatedTasks("after"); } private function _collectRelatedTasks($prop_name) { $arr = array(); foreach(get_tasks() 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 _parse_taskstr($deps); return array(); } 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 _collect_tasks() { global $TASKMAN_TASKS; global $TASKMAN_TASK_ALIASES; $TASKMAN_TASKS = array(); $TASKMAN_TASK_ALIASES = array(); $cands = _get_task_candidates(); foreach($cands as $name => $args) { if(isset($TASKMAN_TASKS[$name])) throw new TaskmanException("Task '$name' is already defined"); 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 _get_task_candidates() { global $TASKMAN_CLOSURES; $cands = array(); //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 get_task($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 _resolve_callable_prop($name) { $prop = $GLOBALS['TASKMAN_PROP_' . $name]; if(!($prop instanceof \Closure)) return; $value = $prop(); $GLOBALS['TASKMAN_PROP_' . $name] = $value; } function get($name) { if(!isset($GLOBALS['TASKMAN_PROP_' . $name])) throw new TaskmanException("Property '$name' is not set"); _resolve_callable_prop($name); return $GLOBALS['TASKMAN_PROP_' . $name]; } function getor($name, $def) { if(!isset($GLOBALS['TASKMAN_PROP_' . $name])) return $def; _resolve_callable_prop($name); return $GLOBALS['TASKMAN_PROP_' . $name]; } function set($name, $value) { $GLOBALS['TASKMAN_PROP_' . $name] = $value; } function setor($name, $value) { if(!isset($GLOBALS['TASKMAN_PROP_' . $name])) $GLOBALS['TASKMAN_PROP_' . $name] = $value; } function is($name) { return isset($GLOBALS['TASKMAN_PROP_' . $name]); } function del($name) { unset($GLOBALS['TASKMAN_PROP_' . $name]); } function props() { $props = array(); foreach($GLOBALS as $key => $value) { if(($idx = strpos($key, 'TASKMAN_PROP_')) === 0) { $name = substr($key, strlen('TASKMAN_PROP_')); $props[$name] = get($name); } } return $props; } function task($name) { global $TASKMAN_CLOSURES; $args = func_get_args(); $TASKMAN_CLOSURES[$name] = $args; } function get_tasks() { global $TASKMAN_TASKS; return $TASKMAN_TASKS; } function current_task() { global $TASKMAN_CURRENT_TASK; return $TASKMAN_CURRENT_TASK; } function run($task, array $args = array()) { if($task instanceof TaskmanTask) $task_obj = $task; else $task_obj = get_task($task); return $task_obj->run($args); } function run_many($tasks, $args = array()) { foreach($tasks as $task_spec) { if(is_array($task_spec)) run($task_spec[0], $task_spec[1]); else run($task_spec, $args); } } function msg_dbg($msg) { _log($msg, 2); } function msg($msg) { _log($msg, 1); } function msg_sys($msg) { _log($msg, 0); } function _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 _($str) { if(strpos($str, '%') === false) return $str; $str = preg_replace_callback( '~%\(([^\)]+)\)%~', function($m) { return get($m[1]); }, $str ); return $str; } function _isset_task($task) { global $TASKMAN_TASKS; global $TASKMAN_TASK_ALIASES; return isset($TASKMAN_TASKS[$task]) || isset($TASKMAN_TASK_ALIASES[$task]); } function _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; } //e.g: run,build,zip function _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 _process_argv(&$argv) { global $TASKMAN_LOG_LEVEL; global $TASKMAN_BATCH; global $TASKMAN_NO_DEPS; $filtered = array(); $process_defs = false; for($i=0;$ihasProp('always')) array_unshift($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 = _parse_taskstr($task_str); if(count($tasks) == 1 && !_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 = _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); } } run_many($tasks, $argv); } else if($default_task) $default_task->run($argv); msg_sys("***** All done (".round(microtime(true)-$GLOBALS['TASKMAN_START_TIME'],2)." sec.) *****\n"); } function usage($script_name = "") { \_taskman_default_usage($script_name); } task('help', function($args = array()) { $filter = ''; if(isset($args[0])) $filter = $args[0]; $maxlen = -1; $tasks = array(); $all = get_tasks(); 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"; }); task('props', function($args = []) { $filter = ''; if(isset($args[0])) $filter = $args[0]; $props = props(); echo "\n"; echo "Available props:\n"; foreach($props as $k => $v) { if($filter && stripos($k, $filter) === false) continue; echo "---------------------------------\n"; echo "$k : " . var_export($v, true) . "\n"; } echo "\n"; }); } //namespace taskman //}}}