Permalink
Browse files

Initial revision

  • Loading branch information...
0 parents commit b213c935e75ffb42bb55c278a9a7ec55826de4be @jaz303 committed Feb 8, 2009
Showing with 435 additions and 0 deletions.
  1. +1 −0 .gitignore
  2. +52 −0 Phakefile
  3. +204 −0 lib/build.php
  4. +37 −0 lib/global_helpers.php
  5. +30 −0 lib/option_parser.php
  6. +111 −0 run.php
1 .gitignore
@@ -0,0 +1 @@
+.DS_Store
52 Phakefile
@@ -0,0 +1,52 @@
+<?php
+desc('Load the application environment');
+task('environment', function() {
+ echo "I am the outer environment. I should run first.\n";
+});
+
+desc('Initialises the database connection');
+task('database', function() {
+ echo "I am initialising the database...\n";
+});
+
+group('test', function() {
+
+ // 'environment' dependency for this task is resolved locally to
+ // task in same group. There is no 'database' task defined in this
+ // group so it drops back to a search of the root group.
+ desc('Run the unit tests');
+ task('units', 'environment', ':environment', 'database', function() {
+ echo "Running unit tests...\n";
+ });
+
+ // another level of nesting; application object is passed to all
+ // executing tasks
+ desc('Run absolutely every test everywhere!');
+ group('all', function() {
+ task('run', 'test:units', function($application) {
+ echo "All tests complete! ($application)\n";
+ });
+ });
+
+});
+
+// duplicate group definitions are merged
+group('test', function() {
+
+ // duplicate task definitions are merged
+ // (although the first description takes precedence when running with -T)
+ desc("You won't see this description");
+ task('units', function() {
+ echo "Running a second batch of unit tests...\n";
+ });
+
+ // use ':environment' to refer to task in root group
+ // we currently have no cyclic dependency checking, you have been warned.
+ task('environment', ':environment', function() {
+ echo "I am the inner environment. I should run second.\n";
+ });
+
+});
+
+task('default', 'test:all:run');
+?>
204 lib/build.php
@@ -0,0 +1,204 @@
+<?php
+namespace Phake;
+
+class TaskNotFoundException extends \Exception {};
+class TaskCollisionException extends \Exception {};
+
+class Application
+{
+ private static $instance = null;
+
+ public static function instance() {
+ if (!isset(self::$instance)) {
+ self::$instance = new Application;
+ }
+ return self::$instance;
+ }
+
+ private $root;
+
+ public function __construct() {
+ $this->root = new Group($null, '');
+ }
+
+ public function root() {
+ return $this->root;
+ }
+
+ public function invoke($task_name, $relative_to = null) {
+ $this->resolve($task_name, $relative_to)->invoke($this);
+ }
+
+ public function reset() {
+ $this->root->reset();
+ }
+
+ public function resolve($task_name, $relative_to = null) {
+ if ($task_name[0] != ':') {
+ if ($relative_to) {
+ try {
+ return $relative_to->resolve(explode(':', $task_name));
+ } catch (TaskNotFoundException $tnfe) {}
+ }
+ } else {
+ $task_name = substr($task_name, 1);
+ }
+ return $this->root->resolve(explode(':', $task_name));
+ }
+
+ public function get_task_list() {
+ $list = array();
+ $this->root->fill_task_list($list);
+ ksort($list);
+ return $list;
+ }
+
+ public function __toString() {
+ return '<' . get_class($this) . '>';
+ }
+}
+
+class Node
+{
+ private $parent;
+ private $name;
+
+ protected $children = array();
+
+ public function __construct($parent, $name) {
+ $this->parent = $parent;
+ $this->name = $name;
+ }
+
+ public function get_name($name) {
+ return $this->name;
+ }
+
+ public function get_parent() {
+ return $this->parent;
+ }
+
+ public function resolve($task_name_parts) {
+ if (count($task_name_parts) == 0) {
+ return $this;
+ } else {
+ $try = array_shift($task_name_parts);
+ if (isset($this->children[$try])) {
+ return $this->children[$try]->resolve($task_name_parts);
+ } else {
+ throw new TaskNotFoundException;
+ }
+ }
+ }
+}
+
+class Group extends Node
+{
+ public function add_group($name) {
+ if (!isset($this->children[$name])) {
+ $this->children[$name] = new Group($this, $name);
+ } elseif (!($this->children[$name] instanceof Group)) {
+ throw new TaskCollisionException("Can't create group '$name', already defined as something else");
+ }
+ return $this->children[$name];
+ }
+
+ public function add_task($name, Task $t) {
+ if (!isset($this->children[$name])) {
+ $this->children[$name] = new TaskWrapper($this, $name);
+ } elseif (!($this->children[$name] instanceof TaskWrapper)) {
+ throw new TaskCollisionException("Can't create task '$name', already defined as something else");
+ }
+ $this->children[$name]->push($t);
+ }
+
+ public function reset() {
+ foreach ($this->children as $c) $c->reset();
+ }
+
+ public function fill_task_list(&$out, $prefix = '') {
+ foreach ($this->children as $name => $child) {
+ if ($child instanceof TaskWrapper) {
+ if ($desc = $child->get_description()) {
+ $out[$prefix . $name] = $desc;
+ }
+ } else {
+ $child->fill_task_list($out, "{$prefix}{$name}:");
+ }
+ }
+ }
+}
+
+class TaskWrapper extends Node
+{
+ private $tasks = array();
+
+ public function push($task) {
+ $this->tasks[] = $task;
+ }
+
+ public function dependencies() {
+ $deps = array();
+ foreach ($this->tasks as $t) {
+ $deps = array_merge($deps, $t->dependencies());
+ }
+ return $deps;
+ }
+
+ public function get_description() {
+ foreach ($this->tasks as $t) {
+ if ($desc = $t->get_description()) return $desc;
+ }
+ return null;
+ }
+
+ public function reset() {
+ foreach ($this->tasks as $t) $t->reset();
+ }
+
+ public function invoke($application) {
+ foreach ($this->dependencies() as $d) $application->invoke($d, $this->get_parent());
+ foreach ($this->tasks as $t) $t->invoke($application);
+ }
+
+}
+
+class Task extends Node
+{
+ private $lambda;
+ private $dependencies;
+ private $description = null;
+ private $has_run = false;
+
+ public function __construct($lambda = null, $dependencies = array()) {
+ $this->lambda = $lambda;
+ $this->dependencies = $dependencies;
+ }
+
+ public function get_description() {
+ return $this->description;
+ }
+
+ public function set_description($d) {
+ $this->description = $d;
+ }
+
+ public function dependencies() {
+ return $this->dependencies;
+ }
+
+ public function reset() {
+ $this->has_run = false;
+ }
+
+ public function invoke($application) {
+ if (!$this->has_run) {
+ if ($this->lambda) {
+ $lambda = $this->lambda;
+ $lambda($application);
+ }
+ $this->has_run = true;
+ }
+ }
+}
+?>
37 lib/global_helpers.php
@@ -0,0 +1,37 @@
+<?php
+$GLOBALS['__CONTEXT__'] = array(\Phake\Application::instance()->root());
+$GLOBALS['__DESC__'] = null;
+
+function push_task($name, $task) {
+ if ($GLOBALS['__DESC__'] !== null) {
+ $task->set_description($GLOBALS['__DESC__']);
+ $GLOBALS['__DESC__'] = null;
+ }
+ active_group()->add_task($name, $task);
+}
+
+function active_group() {
+ return $GLOBALS['__CONTEXT__'][count($GLOBALS['__CONTEXT__']) - 1];
+}
+
+function task() {
+ $deps = func_get_args();
+ $name = array_shift($deps);
+ if ($deps[count($deps) - 1] instanceof Closure) {
+ $work = array_pop($deps);
+ } else {
+ $work = null;
+ }
+ push_task($name, new \Phake\Task($work, $deps));
+}
+
+function group($name, $lambda = null) {
+ $GLOBALS['__CONTEXT__'][] = active_group()->add_group($name);
+ if ($lambda !== null) $lambda();
+ array_pop($GLOBALS['__CONTEXT__']);
+}
+
+function desc($desc) {
+ $GLOBALS['__DESC__'] = $desc;
+}
+?>
30 lib/option_parser.php
@@ -0,0 +1,30 @@
+<?php
+namespace Phake;
+
+class OptionParser
+{
+ private $args;
+ private $equiv;
+ private $array_pos = 0;
+ private $string_pos = null;
+
+ public function __construct($args, $equiv = array()) {
+ $this->args = $args;
+ $this->equiv = array();
+ }
+
+ public function next_option() {
+ if ($this->array_pos >= count($args)) {
+ return false;
+ }
+ }
+
+ public function next_value() {
+
+ }
+
+ public function remainder() {
+
+ }
+}
+?>
111 run.php
@@ -0,0 +1,111 @@
+#!/usr/local/bin/php
+<?php
+require dirname(__FILE__) . '/lib/build.php';
+require dirname(__FILE__) . '/lib/global_helpers.php';
+require dirname(__FILE__) . '/lib/option_parser.php';
+
+define('RUNFILE', 'Phakefile');
+
+try {
+
+ //
+ // Defaults
+
+ $action = 'invoke';
+ $task_names = array('default');
+ $trace = false;
+
+ //
+ // Parse opts, getopt() in PHP blows goats as it can't update argv...
+
+ $equivalence = array('t' => 'trace', 'T' => 'tasks');
+ $args = $GLOBALS['argv'];
+ array_shift($args);
+ $parser = new \Phake\OptionParser($args, $equivalence);
+ while ($option = $parser->next_option()) {
+ switch ($option) {
+ case 'trace':
+ $trace = true;
+ break;
+ case 'tasks':
+ $action = 'list';
+ break;
+ default:
+ throw new Exception("Unknown command line option\n");
+ }
+ }
+
+ $remainder = $parser->remainder();
+ if (count($remainder)) $task_names = $remainder;
+
+ //
+ // Locate runfile
+
+ $directory = getcwd();
+ do {
+ if (file_exists($directory . '/' . RUNFILE)) {
+ break;
+ } elseif (__is_root($directory)) {
+ throw new Exception("No Phakefile found");
+ } else {
+ $directory = dirname($directory);
+ }
+ } while (true);
+
+ if (!@chdir($directory)) {
+ throw new Exception("Couldn't change to directory '$directory'");
+ } else {
+ echo "(in $directory)\n";
+ }
+
+ __load_runfile(RUNFILE);
+
+ //
+ // Go, go, go
+
+ $application = \Phake\Application::instance();
+ $application->reset();
+
+ switch ($action) {
+ case 'list':
+ $task_list = $application->get_task_list();
+ $max = max(array_map('strlen', array_keys($task_list)));
+ foreach ($task_list as $name => $desc) {
+ echo str_pad($name, $max + 4) . $desc . "\n";
+ }
+ break;
+ case 'invoke':
+ foreach ($task_names as $task_name) {
+ $application->invoke($task_name);
+ }
+ break;
+ }
+
+} catch (\Phake\TaskNotFoundException $tnfe) {
+ __fatal($tnfe, "Don't know how to build task '$task_name'\n");
+} catch (Exception $e) {
+ __fatal($e);
+}
+
+function __load_runfile($file) {
+ require $file;
+}
+
+function __fatal($exception, $message = null) {
+ echo "aborted!\n";
+ if (!$message) $message = $exception->getMessage();
+ if (!$message) $message = get_class($exception);
+ echo $message . "\n\n";
+ global $trace;
+ if ($trace) {
+ echo $exception->getTraceAsString() . "\n";
+ } else {
+ echo "(See full trace by running task with --trace)\n";
+ }
+ die(1);
+}
+
+function __is_root($dir) {
+ return $dir == '/';
+}
+?>

0 comments on commit b213c93

Please sign in to comment.