├── .gitattributes
├── tests
├── Bob
│ └── Test
│ │ ├── fixtures
│ │ ├── file_task
│ │ │ ├── in1.txt
│ │ │ ├── in2.txt
│ │ │ └── out.txt
│ │ ├── Bobfile.php
│ │ ├── bob_config.php
│ │ └── tasks
│ │ │ ├── bar.php
│ │ │ └── foo.php
│ │ ├── TaskRegistryTest.php
│ │ ├── TaskTest.php
│ │ ├── TaskLibraryTest.php
│ │ ├── FileTaskTest.php
│ │ └── ApplicationTest.php
└── bootstrap.php
├── .gitignore
├── bin
├── bob
└── bootstrap.php
├── .travis.yml
├── lib
├── Bob
│ ├── TaskLibraryInterface.php
│ ├── TaskRegistry.php
│ ├── TaskInvocationChain.php
│ ├── Library
│ │ ├── TestingLibrary.php
│ │ └── ComposerLibrary.php
│ ├── FileTask.php
│ ├── ConfigFile.php
│ ├── Task.php
│ ├── Cli.php
│ ├── Application.php
│ └── Dsl.php
└── Bob.php
├── phpunit.xml.dist
├── composer.json
├── LICENSE.txt
├── bob_config.php
├── README.md
└── composer.lock
/.gitattributes:
--------------------------------------------------------------------------------
1 | /bin/bob.phar export-ignore
2 |
--------------------------------------------------------------------------------
/tests/Bob/Test/fixtures/file_task/in1.txt:
--------------------------------------------------------------------------------
1 | Foo
2 |
3 |
--------------------------------------------------------------------------------
/tests/Bob/Test/fixtures/file_task/in2.txt:
--------------------------------------------------------------------------------
1 | Bar
2 |
3 |
--------------------------------------------------------------------------------
/tests/Bob/Test/fixtures/file_task/out.txt:
--------------------------------------------------------------------------------
1 | Foo
2 | Bar
3 |
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /phpunit.xml
2 | /vendor/
3 | /coverage/
4 | /composer.phar
5 |
--------------------------------------------------------------------------------
/bin/bob:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 |
2 |
3 |
4 | ./tests
5 |
6 |
7 |
8 |
9 | lib
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/lib/Bob/TaskRegistry.php:
--------------------------------------------------------------------------------
1 | name, $task);
21 | }
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/bin/bootstrap.php:
--------------------------------------------------------------------------------
1 | application;
22 |
23 | exit($cli->run());
24 |
25 |
--------------------------------------------------------------------------------
/tests/Bob/Test/TaskRegistryTest.php:
--------------------------------------------------------------------------------
1 | registry = new TaskRegistry;
14 | }
15 |
16 | function testOffsetGetReturnsNullWhenNoTask()
17 | {
18 | $this->assertNull($this->registry['foo']);
19 | }
20 |
21 | function testRegisterTreatsNamePropertyAsKey()
22 | {
23 | $task = (object) array(
24 | 'name' => 'foo'
25 | );
26 |
27 | $this->registry[] = $task;
28 |
29 | $this->assertEquals($task, $this->registry['foo']);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/Bob/Test/TaskTest.php:
--------------------------------------------------------------------------------
1 | actions[] = function() use (&$invoked) {
15 | $invoked++;
16 | };
17 |
18 | $t->invoke();
19 | $t->invoke();
20 |
21 | $this->assertEquals(1, $invoked);
22 |
23 | $t->reenable();
24 | $t->invoke();
25 | $this->assertEquals(2, $invoked);
26 | }
27 |
28 | function isNeededReturnsTrue()
29 | {
30 | $t = new Task('foo', new Application);
31 | $this->assertEquals(true, $t->isNeeded());
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lib/Bob/TaskInvocationChain.php:
--------------------------------------------------------------------------------
1 | objectMap = new SplObjectStorage;
15 | $this->stack = new SplStack;
16 | }
17 |
18 | function push($task)
19 | {
20 | $this->stack->push($task);
21 | $this->objectMap->attach($task);
22 | }
23 |
24 | function pop()
25 | {
26 | $task = $this->stack->pop();
27 | $this->objectMap->detach($task);
28 |
29 | return $task;
30 | }
31 |
32 | function has($task)
33 | {
34 | return $this->objectMap->contains($task);
35 | }
36 |
37 | function getIterator()
38 | {
39 | return $this->stack;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/lib/Bob/Library/TestingLibrary.php:
--------------------------------------------------------------------------------
1 | fileTask('phpunit.xml', array($app['testing.dist_config']), function($task) {
26 | copy($task->prerequisites->current(), $task->name);
27 | });
28 |
29 | $app->task('test', array('phpunit.xml'), function() use ($app) {
30 | b\sh($app['testing.phpunit_bin'], array('fail_on_error' => true));
31 |
32 | })->description = "Run test suite";
33 | }
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chh/bob",
3 | "description": "A tiny and messy build automation tool for PHP projects.",
4 | "keywords": [
5 | "build"
6 | ],
7 | "license": "MIT",
8 | "homepage": "https://github.com/CHH/Bob",
9 | "authors": [
10 | {
11 | "name": "Christoph Hochstrasser",
12 | "email": "christoph.hochstrasser@gmail.com"
13 | }
14 | ],
15 | "require": {
16 | "php": ">=5.3.3",
17 | "symfony/finder": "~2.0",
18 | "symfony/process": "~2.0",
19 | "chh/optparse": "~0.1",
20 | "chh/fileutils": "~1.0",
21 | "monolog/monolog": "~1.0",
22 | "pimple/pimple": "~1.0",
23 | "chh/itertools": "~1.0"
24 | },
25 | "require-dev": {
26 | "phpunit/phpunit": "~3.7"
27 | },
28 | "autoload": {
29 | "psr-0": {
30 | "Bob\\": "lib/"
31 | }
32 | },
33 | "bin": [
34 | "bin/bob"
35 | ],
36 | "extra": {
37 | "branch-alias": {
38 | "dev-master": "1.0.x-dev"
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2011 Christoph Hochstrasser
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lib/Bob/FileTask.php:
--------------------------------------------------------------------------------
1 | name)
17 | or $this->getTimestamp() > @filemtime($this->name);
18 | }
19 |
20 | # Internal: Returns the timestamp when the prerequisites were last modified.
21 | #
22 | # Returns the time as Unix Epoche represented by an Integer.
23 | function getTimestamp()
24 | {
25 | $lastModifiedTimes = iterator_to_array(itertools\filter(itertools\map(
26 | function($file) {
27 | if (file_exists($file)) {
28 | return @filemtime($file);
29 | }
30 | },
31 | $this->prerequisites
32 | )));
33 |
34 | if ($lastModifiedTimes) {
35 | return max($lastModifiedTimes);
36 | }
37 |
38 | return 0;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib/Bob/ConfigFile.php:
--------------------------------------------------------------------------------
1 | task('composer.phar', function($task) {
18 | if (file_exists($task->name)) {
19 | return true;
20 | }
21 |
22 | $src = fopen('http://getcomposer.org/composer.phar', 'rb');
23 | $dest = fopen($task->name, 'wb');
24 |
25 | stream_copy_to_stream($src, $dest);
26 | });
27 |
28 | $app->task('composer:install', array('composer.phar'), function() {
29 | b\php(array('composer.phar', 'install'), null, array('failOnError' => true));
30 | });
31 |
32 | $app->task('composer:update', array('composer.phar'), function() {
33 | b\php(array('composer.phar', 'update'), null, array('failOnError' => true));
34 | });
35 |
36 | $app->fileTask('composer.lock', array('composer.phar', 'composer.json'), function($task) use ($app) {
37 | $app->task('composer:update')->invoke();
38 | });
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/Bob/Test/TaskLibraryTest.php:
--------------------------------------------------------------------------------
1 | application = new \Bob\Application;
15 |
16 | $this->application['log'] = $this->application->share(function() {
17 | $log = new Logger('bob');
18 | $log->pushHandler(new TestHandler);
19 |
20 | return $log;
21 | });
22 |
23 | $this->application['config.file'] = null;
24 | }
25 |
26 | function testRegister()
27 | {
28 | $lib = $this->getMock('\\Bob\\TaskLibraryInterface');
29 |
30 | $lib->expects($this->once())->method('register')
31 | ->with($this->equalTo($this->application));
32 |
33 | $this->application->register($lib);
34 | }
35 |
36 | function testBoot()
37 | {
38 | $lib = $this->getMock('\\Bob\\TaskLibraryInterface');
39 |
40 | $lib->expects($this->once())->method('register')
41 | ->with($this->equalTo($this->application));
42 |
43 | $lib->expects($this->once())->method('boot')
44 | ->with($this->equalTo($this->application));
45 |
46 | $this->application->register($lib);
47 |
48 | $this->application->init();
49 | }
50 | }
51 |
52 |
--------------------------------------------------------------------------------
/tests/Bob/Test/FileTaskTest.php:
--------------------------------------------------------------------------------
1 | getFixturesDir().'/foo.txt', new Application);
18 |
19 | $this->assertTrue($t->isNeeded());
20 | }
21 |
22 | function testIsNeededWhenPrerequisitesAreNewerThanTarget()
23 | {
24 | $t = new FileTask($this->getFixturesDir().'/out.txt', new Application);
25 | $t->enhance(array(
26 | $this->getFixturesDir().'/in1.txt',
27 | $this->getFixturesDir().'/in2.txt')
28 | );
29 |
30 | touch($this->getFixturesDir().'/in1.txt');
31 | touch($this->getFixturesDir().'/out.txt', strtotime('-1 minute'));
32 |
33 | $this->assertTrue($t->isNeeded());
34 | }
35 |
36 | function testIsNotNeededWhenTargetNewerThanPrerequisites()
37 | {
38 | $t = new FileTask($this->getFixturesDir().'/out.txt', new Application);
39 | $t->enhance(array(
40 | $this->getFixturesDir().'/in1.txt',
41 | $this->getFixturesDir().'/in2.txt')
42 | );
43 |
44 | touch($this->getFixturesDir().'/in1.txt', strtotime('-1 minute'));
45 | touch($this->getFixturesDir().'/in2.txt', strtotime('-1 minute'));
46 | touch($this->getFixturesDir().'/out.txt');
47 |
48 | $this->assertFalse($t->isNeeded());
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/bob_config.php:
--------------------------------------------------------------------------------
1 | in(array('lib', 'bin', 'vendor'));
11 |
12 | register(new \Bob\Library\TestingLibrary, array(
13 | 'testing.dist_config' => 'phpunit.xml.dist'
14 | ));
15 |
16 | register(new \Bob\Library\ComposerLibrary);
17 |
18 | # The "default" task is invoked when there's no
19 | # task explicitly given on the command line.
20 | task('default', array('phar'));
21 |
22 | # Note: All file paths used here should be relative to the project
23 | # directory. Bob automatically sets the current working directory
24 | # to the path where the `bob_config.php` resides.
25 |
26 | desc('Compiles a executable, standalone PHAR file');
27 | task('phar', array('composer.lock', 'test', 'bin/bob.phar'));
28 |
29 | task('clean', function() {
30 | file_exists('bin/bob.phar') and unlink('bin/bob.phar');
31 | });
32 |
33 | task('release', function($task) {
34 | if (!$version = @$_ENV['version'] ?: @$_SERVER['BOB_VERSION']) {
35 | failf('No version given');
36 | }
37 |
38 | if (substr($version, 0, 1) === 'v') {
39 | $version = substr($version, 1);
40 | }
41 |
42 | if (!@$_ENV['skip-branching']) {
43 | sh(sprintf('git checkout -b "release/%s"', $version));
44 | }
45 |
46 | info(sprintf('----> Setting version to "v%s"', $version));
47 |
48 | $contents = file_get_contents('lib/Bob.php');
49 |
50 | $contents = preg_replace(
51 | '/(\s*)(const VERSION = ".+")/',
52 | sprintf('\1const VERSION = "v%s"', $version),
53 | $contents
54 | );
55 |
56 | file_put_contents('lib/Bob.php', $contents);
57 | });
58 |
59 | fileTask('bin/bob.phar', $pharFiles, function($task) {
60 | if (file_exists($task->name)) {
61 | unlink($task->name);
62 | }
63 |
64 | $stub = <<<'EOF'
65 | #!/usr/bin/env php
66 | projectDirectory;
76 |
77 | $phar = new \Phar($task->name, 0, basename($task->name));
78 | $phar->startBuffering();
79 |
80 | foreach ($task->prerequisites as $file) {
81 | $file = (string) $file;
82 | $phar->addFile($file, Path::relativize($file, $projectDir));
83 | }
84 |
85 | $phar->setStub($stub);
86 | $phar->stopBuffering();
87 |
88 | chmod($task->name, 0555);
89 |
90 | println(sprintf(
91 | 'Regenerated Archive "%s" with %d entries', basename($task->name), count($phar)
92 | ));
93 | unset($phar);
94 | });
95 |
96 | desc('Does a system install of Bob, by default to /usr/local/bin');
97 | task('install', array('bin/bob.phar'), function($task) {
98 | $prefix = getenv('PREFIX') ?: '/usr/local';
99 |
100 | $success = copy('bin/bob.phar', "$prefix/bin/bob");
101 | chmod("$prefix/bin/bob", 0755);
102 |
103 | println(sprintf('Installed the `bob` executable in %s.', $prefix));
104 | });
105 |
106 | desc('Removes the `bob` excutable from the PREFIX');
107 | task('uninstall', function($task) {
108 | $prefix = getenv("PREFIX") ?: "/usr/local";
109 |
110 | if (!file_exists("$prefix/bin/bob")) {
111 | println("Seems that bob is not installed. Aborting.", STDERR);
112 | return 1;
113 | }
114 |
115 | unlink("$prefix/bin/bob") and println("Erased bob successfully from $prefix");
116 | });
117 |
118 | task("foo", function() {});
119 |
--------------------------------------------------------------------------------
/tests/Bob/Test/ApplicationTest.php:
--------------------------------------------------------------------------------
1 | application = new \Bob\Application;
15 |
16 | $this->application['log'] = $this->application->share(function() {
17 | $log = new Logger('bob');
18 | $log->pushHandler(new TestHandler);
19 |
20 | return $log;
21 | });
22 |
23 | \Bob::$application = $this->application;
24 | }
25 |
26 | function testTaskShort()
27 | {
28 | $action = function() {
29 | return "bar";
30 | };
31 |
32 | $task = $this->application->task("foo", $action);
33 |
34 | $this->assertTrue($this->application->taskDefined("foo"));
35 | $this->assertInstanceOf("\\Bob\\Task", $task);
36 |
37 | $this->assertEquals("foo", $task->name);
38 | $this->assertNull($task->prerequisites);
39 | $this->assertEquals($action, $task->actions[0]);
40 | }
41 |
42 | function testTaskLong()
43 | {
44 | $action = function() {};
45 | $deps = new \ArrayIterator(array('bar'));
46 |
47 | $task = $this->application->task('foo', $deps, $action);
48 |
49 | $this->assertTrue($this->application->taskDefined('foo'));
50 |
51 | $this->assertEquals($deps, $task->prerequisites);
52 | $this->assertEquals($action, $task->actions[0]);
53 | }
54 |
55 | function testTaskReturnsExistingTaskWhenAlreadyDefined()
56 | {
57 | $task = $this->application->task('foo', function() {});
58 |
59 | $this->assertEquals($task, $this->application->task('foo'));
60 | }
61 |
62 | /**
63 | * @expectedException \InvalidArgumentException
64 | */
65 | function testExecuteThrowsExceptionWhenTaskNotFound()
66 | {
67 | $this->application->execute('foo');
68 | }
69 |
70 | function testExecute()
71 | {
72 | $invoked = 0;
73 |
74 | $this->application->task('foo', array('bar'), function() use (&$invoked) {
75 | $invoked++;
76 | });
77 |
78 | $this->application->task('bar', function() use (&$invoked) {
79 | $invoked++;
80 | });
81 |
82 | $this->application->execute('foo');
83 |
84 | $this->assertEquals(2, $invoked);
85 | }
86 |
87 | function testExecuteSetsWorkingDirectoryToProjectDirectory()
88 | {
89 | $cwd = "";
90 |
91 | $this->application->task('foo', function() use (&$cwd) {
92 | $cwd = getcwd();
93 | });
94 |
95 | $this->application->projectDirectory = __DIR__;
96 |
97 | $this->application->execute('foo');
98 |
99 | $this->assertEquals(__DIR__, $cwd);
100 | }
101 |
102 | function testLoadDefaultRootConfig()
103 | {
104 | $cwd = getcwd();
105 | chdir(__DIR__ . '/fixtures');
106 |
107 | $this->application->init();
108 |
109 | $this->assertTrue($this->application->taskDefined('baz'));
110 | $this->assertContains(__DIR__ . '/fixtures/bob_config.php', $this->application->loadedConfigs);
111 |
112 | chdir($cwd);
113 | }
114 |
115 | function testLoadCustomRootConfig()
116 | {
117 | $cwd = getcwd();
118 | chdir(__DIR__ . '/fixtures');
119 |
120 | $this->application['config.file'] = "Bobfile.php";
121 |
122 | $this->application->init();
123 |
124 | $this->assertTrue($this->application->taskDefined('foobar'));
125 | $this->assertContains(__DIR__ . '/fixtures/Bobfile.php', $this->application->loadedConfigs);
126 |
127 | chdir($cwd);
128 | }
129 |
130 | function testMultipleRootConfigNamesChoosesFirstFound()
131 | {
132 | $cwd = getcwd();
133 | chdir(__DIR__ . '/fixtures');
134 |
135 | $this->application['config.file'] = array("Bobfile.php", "bob_config.php");
136 |
137 | $this->application->init();
138 |
139 | $this->assertTrue($this->application->taskDefined('foobar'));
140 | $this->assertContains(__DIR__ . '/fixtures/Bobfile.php', $this->application->loadedConfigs);
141 |
142 | chdir($cwd);
143 | }
144 |
145 | function testLoadConfigsFromLoadPath()
146 | {
147 | $cwd = getcwd();
148 | chdir(__DIR__ . '/fixtures');
149 |
150 | $this->application['config.load_path'] = array(__DIR__ . '/fixtures/tasks');
151 | $this->application->init();
152 |
153 | $this->assertTrue($this->application->taskDefined('foo'));
154 | $this->assertTrue($this->application->taskDefined('bar'));
155 |
156 | $this->assertContains(__DIR__ . '/fixtures/tasks/foo.php', $this->application->loadedConfigs);
157 | $this->assertContains(__DIR__ . '/fixtures/tasks/bar.php', $this->application->loadedConfigs);
158 |
159 | chdir($cwd);
160 | }
161 | }
162 |
163 |
--------------------------------------------------------------------------------
/lib/Bob/Task.php:
--------------------------------------------------------------------------------
1 | name = $name;
40 | $this->application = $application;
41 |
42 | $this->description = TaskRegistry::$lastDescription;
43 | TaskRegistry::$lastDescription = '';
44 |
45 | $this->clear();
46 | }
47 |
48 | # Child classes of Task should put here their custom logic to determine
49 | # if the task should do something. See the FileTask class for an
50 | # example of this.
51 | #
52 | # Returns TRUE if the task should be run, FALSE otherwise.
53 | function isNeeded()
54 | {
55 | return true;
56 | }
57 |
58 | # Public: Collects all dependencies and invokes the task if it's
59 | # needed.
60 | #
61 | # Returns the callback's return value.
62 | function invoke()
63 | {
64 | if (!$this->enable) {
65 | if ($this->application->trace) {
66 | $this->application['log']->debug("{$this->inspect()} is not enabled");
67 | }
68 | return;
69 | }
70 |
71 | if (!$this->reenable and $this->application['invocation_chain']->has($this)) {
72 | return;
73 | }
74 |
75 | if (!$this->application->forceRun and !$this->isNeeded()) {
76 | $this->application->trace and $this->application['log']->debug("Skipping {$this->inspect()}");
77 | return;
78 | }
79 |
80 | $this->application['invocation_chain']->push($this);
81 |
82 | if ($this->application->trace) {
83 | $this->application['log']->debug("Invoke {$this->inspect()}");
84 | }
85 |
86 | $app = $this->application;
87 |
88 | itertools\walk(
89 | itertools\filter(itertools\to_iterator($this->prerequisites), function($p) use ($app) {
90 | return $app->taskDefined((string) $p);
91 | }),
92 | function($p) use ($app) {
93 | $app['tasks'][(string) $p]->invoke();
94 | }
95 | );
96 |
97 | if ($this->prerequisites !== null) {
98 | $this->prerequisites->rewind();
99 | }
100 |
101 | $this->execute();
102 | $this->reenable = false;
103 | }
104 |
105 | # Internal: Executes all actions.
106 | #
107 | # Returns nothing.
108 | function execute()
109 | {
110 | foreach ($this->actions as $action) {
111 | call_user_func($action, $this);
112 | }
113 | }
114 |
115 | # Clears all actions and prerequisites.
116 | #
117 | # Returns nothing.
118 | function clear()
119 | {
120 | $this->actions = new \SplDoublyLinkedList;
121 | $this->prerequisites = null;
122 | }
123 |
124 | function reenable()
125 | {
126 | $this->reenable = true;
127 | }
128 |
129 | function enhance($deps = null, $action = null)
130 | {
131 | if ($deps) {
132 | if ($this->prerequisites instanceof \Iterator) {
133 | $p = new \AppendIterator;
134 | $p->append($this->prerequisites);
135 | $p->append(itertools\to_iterator($deps));
136 | $this->prerequisites = $p;
137 | } else {
138 | $this->prerequisites = itertools\to_iterator($deps);
139 | }
140 | }
141 |
142 | if (is_callable($action)) {
143 | $this->actions->push($action);
144 | }
145 | }
146 |
147 | function getTaskPrerequisites()
148 | {
149 | $app = $this->application;
150 |
151 | return itertools\filter(
152 | itertools\to_iterator($this->prerequisites),
153 | function($task) use ($app) {
154 | return $app->taskDefined($task);
155 | }
156 | );
157 | }
158 |
159 | function inspect()
160 | {
161 | $prereqs = join(', ', iterator_to_array($this->getTaskPrerequisites()));
162 | $class = get_class($this);
163 | return "<$class {$this->name} => $prereqs>";
164 | }
165 |
166 | function __toString()
167 | {
168 | return $this->name;
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/lib/Bob/Cli.php:
--------------------------------------------------------------------------------
1 | application = new Application;
21 |
22 | $this->opts = new Optparse\Parser;
23 | $this->opts
24 | ->addFlag('init', array("alias" => '-i'))
25 | ->addFlag('version')
26 | ->addFlag('help', array("alias" => '-h'))
27 | ->addFlag('tasks', array("alias" => '-t'))
28 | ->addFlag('chdir', array("alias" => '-C', "has_value" => true))
29 | ->addFlag('verbose', array("alias" => '-v'))
30 | ->addFlagVar('all', $this->showAllTasks, array("alias" => '-A'))
31 | ->addFlagVar('trace', $this->trace, array("alias" => '-T'))
32 | ->addFlagVar('force', $this->forceRun, array("alias" => '-f'))
33 | ;
34 | }
35 |
36 | function run($argv = null)
37 | {
38 | try {
39 | $this->opts->parse($argv);
40 | } catch (Optparse\Exception $e) {
41 | fwrite(STDERR, "{$e->getMessage()}\n\n");
42 | fwrite(STDERR, $this->formatUsage());
43 | return 1;
44 | }
45 |
46 | $this->application['log.verbose'] = (bool) $this->opts['verbose'];
47 |
48 | $this->application->trace = $this->trace;
49 | $this->application->forceRun = $this->forceRun;
50 |
51 | if ($this->opts['version']) {
52 | printf("Bob %s\n", \Bob::VERSION);
53 | return 0;
54 | }
55 |
56 | if ($this->opts["init"]) {
57 | return $this->initProject() ? 0 : 1;
58 | }
59 |
60 | if ($this->opts["help"]) {
61 | fwrite(STDERR, $this->formatUsage());
62 | return 0;
63 | }
64 |
65 | if ($dir = $this->opts["chdir"]) {
66 | if (!is_dir($dir)) {
67 | $this->logger()->err(sprintf('Directory not found: "%s"', $dir));
68 | return 1;
69 | }
70 |
71 | $this->logger()->info(sprintf('Changing working directory to "%s"', realpath($dir)));
72 |
73 | chdir($dir);
74 | }
75 |
76 | $this->application['config.load_path'] = array_merge($this->application['config.load_path'], explode(':', @$_SERVER['BOB_CONFIG_PATH']));
77 |
78 | try {
79 | $this->application->init();
80 | } catch (\Exception $e) {
81 | fwrite(STDERR, $e);
82 | return 127;
83 | }
84 |
85 | if ($this->opts["tasks"]) {
86 | fwrite(STDERR, $this->formatTasksAndDescriptions());
87 | return 0;
88 | }
89 |
90 | $this->withErrorHandling(array($this, 'runTasks'));
91 | }
92 |
93 | function logger()
94 | {
95 | return $this->application['log'];
96 | }
97 |
98 | function withErrorHandling($callback)
99 | {
100 | try {
101 | call_user_func($callback);
102 | return 0;
103 | } catch (\Exception $e) {
104 | $this->logger()->err(sprintf(
105 | "Build failed: %s (use --trace to get a stack trace)", $e->getMessage())
106 | );
107 |
108 | if ($this->trace) {
109 | $this->logger()->info($e->getTraceAsString());
110 | }
111 |
112 | return 1;
113 | }
114 | }
115 |
116 | protected function runTasks()
117 | {
118 | return $this->application->execute($this->collectTasks());
119 | }
120 |
121 | protected function collectTasks()
122 | {
123 | $tasks = array();
124 | $args = $this->opts->args();
125 |
126 | foreach ($args as $arg) {
127 | if (preg_match('/^(\w+)=(.*)$/', $arg, $matches)) {
128 | $this->application->env[$matches[1]] = trim($matches[2], '"');
129 | continue;
130 | }
131 |
132 | $tasks[] = $arg;
133 | }
134 |
135 | if (empty($tasks)) {
136 | $tasks += array('default');
137 | }
138 |
139 | return $tasks;
140 | }
141 |
142 | protected function formatUsage()
143 | {
144 | $version = \Bob::VERSION;
145 | $bin = basename($_SERVER['SCRIPT_NAME']);
146 |
147 | return <<:
174 | Changes the working directory to before running tasks.
175 | -T|--trace:
176 | Logs trace messages to STDERR
177 | -f|--force:
178 | Force to run all tasks, even if they're not needed
179 | -v|--verbose:
180 | Be more verbose.
181 | -h|--help:
182 | Displays this message
183 |
184 |
185 | HELPTEXT;
186 | }
187 |
188 | protected function formatTasksAndDescriptions()
189 | {
190 | $tasks = $this->application['tasks']->getArrayCopy();
191 | ksort($tasks);
192 |
193 | $text = '';
194 | $text .= "(in {$this->application->projectDirectory})\n";
195 |
196 | foreach ($tasks as $name => $task) {
197 | if ($name === 'default' || (!$task->description && !$this->showAllTasks)) {
198 | continue;
199 | }
200 |
201 | if ($task instanceof FileTask) {
202 | $text .= "File => {$task->name}";
203 | } else {
204 | $text .= "bob {$task->name}";
205 | }
206 |
207 | $text .= "\n";
208 |
209 | if ($task->description) {
210 | $text .= "\t{$task->description}\n";
211 | }
212 | }
213 |
214 | return $text;
215 | }
216 |
217 | protected function initProject()
218 | {
219 | if (file_exists("bob_config.php")) {
220 | fwrite(STDERR, "Project already has a bob_config.php\n");
221 | return false;
222 | }
223 |
224 | $config = <<<'EOF'
225 | share(function() {
42 | return new TaskRegistry;
43 | });
44 |
45 | $this['config.load_path'] = array('./bob_tasks');
46 | $this['config.file'] = 'bob_config.php';
47 | $this['default_task_class'] = "\\Bob\\Task";
48 |
49 | $this['task_factory'] = $this->protect(function($name) use ($app) {
50 | $action = null;
51 | $prerequisites = null;
52 | $class = $app['default_task_class'];
53 |
54 | foreach (array_filter(array_slice(func_get_args(), 1)) as $arg) {
55 | switch (true) {
56 | case is_callable($arg):
57 | $action = $arg;
58 | break;
59 | case is_string($arg) and class_exists($arg):
60 | $class = $arg;
61 | break;
62 | case is_array($arg):
63 | case ($arg instanceof \Traversable):
64 | case ($arg instanceof \Iterator):
65 | $prerequisites = $arg;
66 | break;
67 | }
68 | }
69 |
70 | if (empty($name)) {
71 | throw new \InvalidArgumentException('Name cannot be empty');
72 | }
73 |
74 | if ($app->taskDefined($name)) {
75 | $task = $app['tasks'][$name];
76 | } else {
77 | $task = new $class($name, $app);
78 | $app->defineTask($task);
79 | }
80 |
81 | $task->enhance($prerequisites, $action);
82 |
83 | return $task;
84 | });
85 |
86 | $this['log.verbose'] = false;
87 |
88 | $this['log'] = $this->share(function() use ($app) {
89 | $log = new Logger("bob");
90 |
91 | $stderrHandler = new StreamHandler(STDERR, $app['log.verbose'] ? Logger::DEBUG : Logger::WARNING);
92 | $stderrHandler->setFormatter(new LineFormatter("%channel%: [%level_name%] %message%" . PHP_EOL));
93 |
94 | $log->pushHandler($stderrHandler);
95 |
96 | return $log;
97 | });
98 |
99 | $this['invocation_chain'] = $this->share(function() {
100 | return new TaskInvocationChain;
101 | });
102 | }
103 |
104 | function register(TaskLibraryInterface $taskLib, array $parameters = array())
105 | {
106 | $taskLib->register($this);
107 | $this->taskLibraries[] = $taskLib;
108 |
109 | foreach ($parameters as $param => $value) {
110 | $this[$param] = $value;
111 | }
112 |
113 | return $this;
114 | }
115 |
116 | function init()
117 | {
118 | $this->loadConfig();
119 |
120 | foreach ($this->taskLibraries as $taskLib) {
121 | $taskLib->boot($this);
122 | }
123 | }
124 |
125 | function task($name, $prerequisites = null, $action = null)
126 | {
127 | return $this['task_factory']($name, $prerequisites, $action);
128 | }
129 |
130 | function fileTask($target, $prerequisites, $action)
131 | {
132 | return $this['task_factory']($target, $prerequisites, $action, "\\Bob\\FileTask");
133 | }
134 |
135 | function execute($tasks)
136 | {
137 | $this->prepareEnv();
138 |
139 | $tasks = (array) $tasks;
140 | $start = microtime(true);
141 |
142 | foreach ($tasks as $taskName) {
143 | if (!$task = $this['tasks'][$taskName]) {
144 | throw new \InvalidArgumentException(sprintf('Task "%s" not found.', $taskName));
145 | }
146 |
147 | $this['log']->info(sprintf(
148 | 'Running Task "%s"', $taskName
149 | ));
150 |
151 | if ($this->projectDirectory) {
152 | Path::chdir($this->projectDirectory, array($task, 'invoke'));
153 | } else {
154 | $task->invoke();
155 | }
156 | }
157 |
158 | $this['log']->info(sprintf('Build finished in %f seconds', microtime(true) - $start));
159 | }
160 |
161 | function taskDefined($task)
162 | {
163 | if (is_object($task) and !empty($task->name)) {
164 | $task = $task->name;
165 | }
166 |
167 | return (bool) $this['tasks'][$task];
168 | }
169 |
170 | function defineTask($task)
171 | {
172 | $this['tasks'][] = $task;
173 | return $this;
174 | }
175 |
176 | protected function prepareEnv()
177 | {
178 | $_ENV = array_merge($_ENV, $this->env);
179 | }
180 |
181 | # Internal: Looks up the build config files from the root of the project
182 | # and from the search dir in `./bob_tasks`. Build Config files contain
183 | # the task definitions.
184 | #
185 | # Returns nothing.
186 | protected function loadConfig()
187 | {
188 | $rootConfigPath = false;
189 |
190 | foreach ((array) $this['config.file'] as $file) {
191 | $rootConfigPath = ConfigFile::findConfigFile($file, getcwd());
192 |
193 | if (false !== $rootConfigPath) {
194 | break;
195 | }
196 | }
197 |
198 | if (false === $rootConfigPath) {
199 | $this['log']->err(sprintf(
200 | "Filesystem boundary reached, none of %s found.\n",
201 | json_encode((array) $this['config.file'])
202 | ));
203 | return false;
204 | }
205 |
206 | $this->loadConfigFile($rootConfigPath);
207 |
208 | # Save the original working directory, the working directory
209 | # gets set to the project directory while running tasks.
210 | $this->originalDirectory = getcwd();
211 |
212 | # The project dir is the directory of the root config.
213 | $this->projectDirectory = dirname($rootConfigPath);
214 |
215 | # Search for additional configs in the config load paths
216 | $configLoadPath = array_filter($this['config.load_path'], 'is_dir');
217 |
218 | if ($configLoadPath) {
219 | $cwd = getcwd();
220 | chdir($this->projectDirectory);
221 |
222 | $finder = Finder::create()
223 | ->files()->name("*.php")
224 | ->in($configLoadPath);
225 |
226 | foreach ($finder as $file) {
227 | $this->loadConfigFile($file);
228 | }
229 |
230 | chdir($cwd);
231 | }
232 |
233 | return true;
234 | }
235 |
236 | # Load config file in its own scope
237 | protected function loadConfigFile($file)
238 | {
239 | if (in_array($file, $this->loadedConfigs)) {
240 | $this['log']->info(sprintf('Skipping: Already loaded config "%s"', $file));
241 | return;
242 | }
243 |
244 | $file = (string) $file;
245 | $app = $this;
246 |
247 | $config = function() use ($file, $app) {
248 | include($file);
249 | };
250 |
251 | $config();
252 |
253 | $this['log']->info(sprintf('Loaded config "%s"', $file));
254 | $this->loadedConfigs[] = $file;
255 | }
256 | }
257 |
258 |
--------------------------------------------------------------------------------
/lib/Bob/Dsl.php:
--------------------------------------------------------------------------------
1 | info($msg);
48 | }
49 |
50 | function register(TaskLibraryInterface $taskLib, array $parameters = array())
51 | {
52 | return \Bob::$application->register($taskLib, $parameters);
53 | }
54 |
55 | # Public: Defines the callback as a task with the given name.
56 | #
57 | # name - Task Name.
58 | # prerequisites - List of Dependency names.
59 | # callback - The task's action, can be any callback.
60 | #
61 | # Examples
62 | #
63 | # task('hello', function() {
64 | # echo "Hello World\n";
65 | # });
66 | #
67 | # Returns nothing.
68 | function task($name, $prerequisites = null, $callback = null)
69 | {
70 | return \Bob::$application->task($name, $prerequisites, $callback);
71 | }
72 |
73 | # Public: Config file function for creating a task which is only run
74 | # when the target file does not exist, or the prerequisites were modified.
75 | #
76 | # target - Filename of the resulting file, this is set as task name. Use
77 | # paths relative to the CWD (the CWD is always set to the root
78 | # of your project for you).
79 | # prerequisites - List of files which are needed to generate the target. The callback
80 | # which generates the target is only run when one of this files is newer
81 | # than the target file. You can access this list from within the task via
82 | # the task's `prerequisites` property.
83 | # callback - Place your logic needed to generate the target here. It's only run when
84 | # the prerequisites were modified or the target does not exist.
85 | #
86 | # Returns a Task instance.
87 | function fileTask($target, $prerequisites = array(), $callback)
88 | {
89 | return \Bob::$application->fileTask($target, $prerequisites, $callback);
90 | }
91 |
92 | # Copies the file only when it doesn't exists or was updated.
93 | #
94 | # source - Source file. This file is watched for changes.
95 | # dest - Destination.
96 | #
97 | # Returns a Task instance.
98 | function copyTask($from, $to)
99 | {
100 | return fileTask($to, array($from), function($task) {
101 | logger()->info("copyTask('{$task->prerequisites[0]}' => '{$task->name}')");
102 |
103 | if (false === copy($task->prerequisites[0], $task->name)) {
104 | fail("Failed copying '{$task->prerequisites[0]}' => '{$task->name}'");
105 | }
106 | });
107 | }
108 |
109 | function directoryTask($directory, $mode = 0777)
110 | {
111 | return task($directory, function($task) use ($directory, $mode) {
112 | if (!is_dir($directory)) {
113 | mkdir($directory, $mode, true);
114 | }
115 | });
116 | }
117 |
118 | # Public: Defines the description of the subsequent task.
119 | #
120 | # desc - Description text, should explain in plain sentences
121 | # what the task does.
122 | #
123 | # Examples
124 | #
125 | # desc('Says Hello World to NAME');
126 | # task('greet', function($task) {
127 | # $operands = Bob::$application->opts->getOperands();
128 | # $name = $operands[1];
129 | #
130 | # echo "Hello World $name!\n";
131 | # });
132 | #
133 | # Returns nothing.
134 | function desc($desc)
135 | {
136 | TaskRegistry::$lastDescription = $desc;
137 | }
138 |
139 | # Public: Appends an End-Of-Line character to the given
140 | # text and writes it to a stream.
141 | #
142 | # line - Text to write.
143 | # stream - Resource to write the text to (optional). By
144 | # default the text is printed to STDOUT via `echo`
145 | #
146 | # Examples
147 | #
148 | # # Print something to STDERR (uses fwrite)
149 | # println('Error', STDERR);
150 | #
151 | # Returns Nothing.
152 | function println($line, $stream = null)
153 | {
154 | $line .= PHP_EOL;
155 |
156 | if (is_resource($stream)) {
157 | fwrite($stream, $line);
158 | } else {
159 | echo "$line";
160 | }
161 | }
162 |
163 | # Public: Renders a PHP template.
164 | #
165 | # file - Template file, this must be a valid PHP file.
166 | #
167 | # Examples
168 | #
169 | # # template.phtml
170 | # Hello = $name ? >
171 | #
172 | # # test.php
173 | # $t = template('template.phtml');
174 | # echo $t(array('name' => 'Christoph'));
175 | # # => Hello Christoph
176 | #
177 | # Returns an anonymous function of the variables, which returns
178 | # the rendered String.
179 | function template($file)
180 | {
181 | if (!file_exists($file)) {
182 | throw \InvalidArgumentException(sprintf(
183 | 'File %s does not exist.', $file
184 | ));
185 | }
186 |
187 | $__file = $file;
188 |
189 | $template = function($__vars) use ($__file) {
190 | extract($__vars);
191 | unset($__vars);
192 |
193 | ob_start();
194 | include($__file);
195 | return ob_get_clean();
196 | };
197 |
198 | return $template;
199 | }
200 |
201 | # Public: Runs a system command. Let's the build fail when the command did exit with a
202 | # status indicating an error.
203 | #
204 | # cmd - Command with arguments as String or List. Lists get joined by a single space.
205 | # callback - A callback which receives the success as Boolean
206 | # and the Process instance as second argument (optional).
207 | # options - Array of options:
208 | # timeout - Timeout in seconds.
209 | # failOnError - Should the build fail when the command failed with a non-zero
210 | # exit status? (default: false)
211 | #
212 | # Examples
213 | #
214 | # # The command's output is displayed on STDOUT and the build fails when the exit code
215 | # # was greater than zero.
216 | # sh('ls -l');
217 | #
218 | # # When a callback is passed as second argument, then the callback
219 | # # receives the success status ($ok) as Boolean and a process instance
220 | # # as second argument.
221 | # sh('ls -A', function($ok, $process) {
222 | # $ok or fwrite($process->getErrorOutput(), STDERR);
223 | # });
224 | #
225 | # Returns nothing.
226 | function sh($cmd, $callback = null, $options = array())
227 | {
228 | $cmd = join(' ', (array) $cmd);
229 | $showCmd = strlen($cmd) > 42 ? "..." . substr($cmd, -42) : $cmd;
230 |
231 | logger()->info("sh($showCmd)");
232 |
233 | if (func_num_args() == 2 and is_array($callback) and !is_callable($callback)) {
234 | $options = $callback;
235 | $callback = null;
236 | }
237 |
238 | $timeout = @$options["timeout"];
239 | $failOnError = @$options["failOnError"] ?: @$options['fail_on_error'];
240 |
241 | $process = new Process($cmd);
242 | $process->setTimeout($timeout);
243 |
244 | $process->run(function($type, $output) {
245 | $type == 'err' ? fwrite(STDERR, $output) : print($output);
246 | });
247 |
248 | if (!$process->isSuccessful() and $failOnError) {
249 | failf("Command failed with status (%d) [%s]", array($process->getExitCode(), $showCmd));
250 | }
251 |
252 | if ($callback !== null) {
253 | call_user_func($callback, $process->isSuccessful(), $process);
254 | }
255 | }
256 |
257 | # Public: Run a PHP Process with the given arguments.
258 | #
259 | # argv - The argv either as Array or String. Arrays get joined by a single space.
260 | # callback - See sh().
261 | # options - See sh().
262 | #
263 | # Examples
264 | #
265 | # # Runs a PHP dev server on `localhost:4000` with the document root
266 | # # `public/` and the router script `public/index.php` (requires PHP >= 5.4).
267 | # php(array('-S', 'localhost:4000', '-t', 'public/', 'public/index.php'));
268 | #
269 | # Returns nothing.
270 | function php($argv, $callback = null, $options = array())
271 | {
272 | $execFinder = new \Symfony\Component\Process\PhpExecutableFinder;
273 | $php = $execFinder->find();
274 |
275 | $argv = (array) $argv;
276 | array_unshift($argv, $php);
277 |
278 | return sh($argv, $callback, $options);
279 | }
280 |
281 | # Public: Creates a new finder from the list of patterns.
282 | #
283 | # patterns - List of shell file patterns.
284 | #
285 | # Returns a new Finder.
286 | function fileList($patterns)
287 | {
288 | $patterns = (array) $patterns;
289 |
290 | $finder = new \Symfony\Component\Finder\Finder;
291 | $finder->files();
292 |
293 | foreach ($patterns as $p) {
294 | $finder->name($p);
295 | }
296 |
297 | return $finder;
298 | }
299 |
300 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Bob, your friendly builder
2 | ==========================
3 |
4 | [](https://travis-ci.org/CHH/bob)
5 |
6 | - - -
7 | ## Hello World
8 |
9 | Put this in a file named `bob_config.php` in your project's root:
10 |
11 | ```php
12 | `.
44 | - Bob **has no** file finder similar to `pakeFinder`, if you need this
45 | just use the [Symfony Finder](https://github.com/symfony/Finder).
46 |
47 | **How Bob compares to [Phing](http://www.phing.info/trac/):**
48 |
49 | - Bob **does not use XML config files** to define tasks. I think build
50 | files should be written in the language used to write the project
51 | itself so the barrier to contribution to build files is as low as possible.
52 | Also I think it's quite hilarious to use XML for a DSL with logic and such.
53 | - Bob has nothing like plugins. To add new functions to Bob's DSL just
54 | put them into the `Bob\BuildConfig` namespace and require the file somehow at the
55 | beginning of your build file. **Simply put:** Bob's build configs are _only_ PHP.
56 | - Bob has **no** rich set of provided tasks and I do not plan to add
57 | this. _Bob is lightweight._
58 |
59 | Getting Started
60 | ---------------
61 |
62 | ### Install
63 |
64 | #### Prerequisites
65 |
66 | Bob needs at least **PHP 5.3.2** to run.
67 |
68 | If you plan to hack on Bob, please make sure you
69 | have set `phar.readonly` to `Off` in your `php.ini`. Otherwise you will have a tough luck
70 | creating a PHAR package of Bob.
71 |
72 | #### Install into a [Composer](https://github.com/composer/composer)-enabled Project (recommended)
73 |
74 | Add the `chh/bob` package to the `require-dev` section in your
75 | `composer.json`:
76 |
77 | {
78 | "require-dev": {
79 | "chh/bob": "1.0.*@dev"
80 | }
81 | }
82 |
83 | Then run `composer install --dev`.
84 |
85 | You can invoke Bob with:
86 |
87 | php vendor/bin/bob
88 |
89 | or:
90 |
91 | ./vendor/bin/bob
92 |
93 | #### System-wide install (Unix-like OS only)
94 |
95 | To do a system-wide install, download either a [Release][tags] or
96 | clone the Repository with:
97 |
98 | $ git clone git://github.com/CHH/bob.git
99 | $ cd Bob
100 |
101 | [tags]: https://github.com/CHH/bob/tags
102 |
103 | To install all of Bob's dependencies download Composer and run
104 | `composer install`:
105 |
106 | $ wget http://getcomposer.org/composer.phar
107 | php composer.phar install
108 |
109 | Then run `php bin/bob install` and you're done.
110 |
111 | By default the `bob` command is created in `/usr/local/bin`. To change
112 | this set a `PREFIX` environment variable, the command is then created
113 | in `$PREFIX/bin`.
114 |
115 | ### Prepare your project
116 |
117 | You can output a usage message by running
118 |
119 | $ php vendor/bin/bob --help
120 |
121 | First run in your projects root directory Bob with the `--init` flag.
122 | This creates an empty `bob_config.php` with one example task:
123 |
124 | $ php vendor/bin/bob --init
125 |
126 | Bob loads your tasks from a special file named `bob_config.php` in your project's
127 | root directory. Bob also includes all files found in a directory
128 | named `bob_tasks`, in the same directory as your `bob_config.php`. Files loaded from
129 | `bob_tasks` are treated the same way as regular Bob configs.
130 |
131 | It's important that you declare that this file belongs to the
132 | `Bob\BuildConfig` namespace with `namespace Bob\BuildConfig;`, otherwise the DSL functions are
133 | not available.
134 |
135 | ---
136 |
137 | **Hint:** It doesn't matter if you're in a sub-directory of your
138 | project, Bob _will_ find your `bob_config.php` by wandering up
139 | the directory tree.
140 |
141 | ---
142 |
143 | Now let's define our first task. This task will output "Hello World":
144 |
145 | task('hello', function() {
146 | println('Hello World');
147 | });
148 |
149 | Tasks are run by using their name as argument(s) on the command line:
150 |
151 | $ php vendor/bin/bob hello
152 |
153 | When Bob is invoked without tasks it tries to invoke
154 | the `default` task.
155 |
156 | To set a task as default task assign the task as prerequisite
157 | of the `default` task:
158 |
159 | task('default', array('hello'));
160 |
161 | You know, tasks should be self-documenting, you really don't want a
162 | manual for your build config or do you? Bob provides the
163 | `desc` function for that. Let's add some text to our task, which describes
164 | what the task is all about:
165 |
166 | desc('Prints Hello World to the Command Line');
167 | task('hello', function() {
168 | println('Hello World');
169 | });
170 |
171 | To view all tasks and their descriptions pass the `--tasks` flag:
172 |
173 | $ php vendor/bin/bob --tasks
174 | bob hello # Prints Hello World to the Command Line
175 |
176 | ---
177 |
178 | To see more examples for how Bob can be used, simply look into Bob's
179 | `bob_config.php`. It contains all configuration to create Bob's build
180 | artifacts, for example the `bob.phar` and the composer config.
181 |
182 | ### File Tasks
183 |
184 | A file task is a special kind of task, which gets only run if either the
185 | target (the product of some operation) does not exist, or the
186 | prerequisites are newer than the target.
187 |
188 | So file tasks are very handy if you've some artifacts which are
189 | generated from other files, and which you don't want to regenerate
190 | if nothing has changed.
191 |
192 | For example: Let's write a task which concatenates three input files to
193 | one output file.
194 |
195 | First we have to create the prerequisites:
196 |
197 | $ echo "foo\n" > file1.txt
198 | $ echo "bar\n" > file2.txt
199 | $ echo "baz\n" > file3.txt
200 |
201 | Then put this into your `bob_config.php`:
202 |
203 | fileTask('concat.txt', array('file1.txt', 'file2.txt', 'file3.txt'), function($task) {
204 | println("Concatenating");
205 | $concat = '';
206 |
207 | foreach ($task->prerequisites as $file) {
208 | $concat .= file_get_contents($file);
209 | }
210 |
211 | @file_put_contents($task->name, $concat);
212 | });
213 |
214 | Let's run this task:
215 |
216 | $ php vendor/bin/bob concat.txt
217 | Concatenating
218 |
219 | This will result in a `concat.txt` file in your project root:
220 |
221 | $ cat concat.txt
222 | foo
223 | bar
224 | baz
225 |
226 | Let's run it again, without modifying the prerequisites:
227 |
228 | $ php vendor/bin/bob concat.txt
229 |
230 | See it? The callback was not run, because the prerequisites were not modified.
231 |
232 | Let's verify this:
233 |
234 | $ touch file1.txt
235 | $ php vendor/bin/bob concat.txt
236 | Concatenating
237 |
238 | The prerequisites of a file task are also resolved as task names, so
239 | they can depend on other file tasks too. Or you can put regular task
240 | names into the prerequisites, but then you've to be careful to not
241 | accidentally treat them as files when looping through all prerequisites.
242 |
243 | ### Packaging tasks for reusability
244 |
245 | Ever did write a collection of tasks which you want to put into a
246 | package and reuse across projects?
247 |
248 | Enter Task Libraries.
249 |
250 | All task libraries implement the `\Bob\TaskLibraryInterface` which
251 | defines two methods:
252 |
253 | * `register(\Bob\Application $app)`: Is called when the library is
254 | registered in the app.
255 | * `boot(\Bob\Application $app)`: is called just before the Bob is run on
256 | the command line. Register your tasks here.
257 |
258 | Here's a small example which registers a `test` task which uses PHPUnit:
259 |
260 | ```php
261 | fileTask("phpunit.xml", array("phpunit.dist.xml"), function($task) {
275 | copy($task->prerequisites->current(), $task->name);
276 | });
277 |
278 | $app->task("test", array("phpunit.xml"), function($task) {
279 | b\sh("./vendor/bin/phpunit");
280 |
281 | })->description = "Runs the test suite";
282 | }
283 | }
284 | ```
285 |
286 | You can use task libraries by calling the `register` function within
287 | your build scripts:
288 |
289 | ```php
290 | =5.3.2"
24 | },
25 | "type": "library",
26 | "extra": {
27 | "branch-alias": {
28 | "dev-master": "1.0.x-dev"
29 | }
30 | },
31 | "autoload": {
32 | "psr-0": {
33 | "CHH\\FileUtils": "lib/"
34 | }
35 | },
36 | "notification-url": "https://packagist.org/downloads/",
37 | "license": [
38 | "MIT"
39 | ],
40 | "authors": [
41 | {
42 | "name": "Christoph Hochstrasser",
43 | "email": "christoph.hochstrasser@gmail.com",
44 | "homepage": "http://christophh.net"
45 | }
46 | ],
47 | "description": "Some utility functions for handling files",
48 | "homepage": "https://github.com/CHH/fileutils",
49 | "keywords": [
50 | "file",
51 | "util"
52 | ],
53 | "time": "2013-01-07 17:31:32"
54 | },
55 | {
56 | "name": "chh/itertools",
57 | "version": "v1.0.0",
58 | "source": {
59 | "type": "git",
60 | "url": "http://github.com/CHH/itertools",
61 | "reference": "v1.0.0"
62 | },
63 | "dist": {
64 | "type": "zip",
65 | "url": "https://github.com/CHH/itertools/archive/v1.0.0.zip",
66 | "reference": "v1.0.0",
67 | "shasum": ""
68 | },
69 | "require": {
70 | "ext-spl": "*",
71 | "php": ">=5.3.3"
72 | },
73 | "require-dev": {
74 | "chh/bob": "*@dev",
75 | "phpunit/phpunit": ">=3.7,<4.0"
76 | },
77 | "type": "library",
78 | "extra": {
79 | "branch-alias": {
80 | "dev-master": "1.0.x-dev"
81 | }
82 | },
83 | "autoload": {
84 | "psr-0": {
85 | "itertools\\": "lib/"
86 | },
87 | "files": [
88 | "lib/itertools.php"
89 | ]
90 | },
91 | "notification-url": "https://packagist.org/downloads/",
92 | "license": [
93 | "MIT"
94 | ],
95 | "authors": [
96 | {
97 | "name": "Christoph Hochstrasser",
98 | "email": "christoph.hochstrasser@gmail.com",
99 | "homepage": "http://christophh.net"
100 | }
101 | ],
102 | "description": "Common functional operations for iterators.",
103 | "keywords": [
104 | "functional",
105 | "iterators"
106 | ],
107 | "time": "2013-01-08 17:10:36"
108 | },
109 | {
110 | "name": "chh/optparse",
111 | "version": "v0.1.0",
112 | "source": {
113 | "type": "git",
114 | "url": "git://github.com/CHH/optparse",
115 | "reference": "v0.1.0"
116 | },
117 | "dist": {
118 | "type": "zip",
119 | "url": "https://github.com/CHH/optparse/archive/v0.1.0.zip",
120 | "reference": "v0.1.0",
121 | "shasum": ""
122 | },
123 | "require": {
124 | "php": ">=5.3.3"
125 | },
126 | "type": "library",
127 | "autoload": {
128 | "classmap": [
129 | "lib/"
130 | ]
131 | },
132 | "notification-url": "https://packagist.org/downloads/",
133 | "license": [
134 | "mit"
135 | ],
136 | "authors": [
137 | {
138 | "name": "Christoph Hochstrasser",
139 | "email": "christoph.hochstrasser@gmail.com",
140 | "homepage": "http://christophh.net"
141 | }
142 | ],
143 | "description": "Easy to use option parser.",
144 | "keywords": [
145 | "cli"
146 | ],
147 | "time": "2012-12-19 18:13:05"
148 | },
149 | {
150 | "name": "monolog/monolog",
151 | "version": "1.7.0",
152 | "source": {
153 | "type": "git",
154 | "url": "https://github.com/Seldaek/monolog.git",
155 | "reference": "6225b22de9dcf36546be3a0b2fa8e3d986153f57"
156 | },
157 | "dist": {
158 | "type": "zip",
159 | "url": "https://api.github.com/repos/Seldaek/monolog/zipball/6225b22de9dcf36546be3a0b2fa8e3d986153f57",
160 | "reference": "6225b22de9dcf36546be3a0b2fa8e3d986153f57",
161 | "shasum": ""
162 | },
163 | "require": {
164 | "php": ">=5.3.0",
165 | "psr/log": "~1.0"
166 | },
167 | "require-dev": {
168 | "aws/aws-sdk-php": "~2.4.8",
169 | "doctrine/couchdb": "dev-master",
170 | "mlehner/gelf-php": "1.0.*",
171 | "phpunit/phpunit": "~3.7.0",
172 | "raven/raven": "0.5.*",
173 | "ruflin/elastica": "0.90.*"
174 | },
175 | "suggest": {
176 | "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
177 | "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
178 | "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
179 | "ext-mongo": "Allow sending log messages to a MongoDB server",
180 | "mlehner/gelf-php": "Allow sending log messages to a GrayLog2 server",
181 | "raven/raven": "Allow sending log messages to a Sentry server",
182 | "ruflin/elastica": "Allow sending log messages to an Elastic Search server"
183 | },
184 | "type": "library",
185 | "extra": {
186 | "branch-alias": {
187 | "dev-master": "1.7.x-dev"
188 | }
189 | },
190 | "autoload": {
191 | "psr-0": {
192 | "Monolog": "src/"
193 | }
194 | },
195 | "notification-url": "https://packagist.org/downloads/",
196 | "license": [
197 | "MIT"
198 | ],
199 | "authors": [
200 | {
201 | "name": "Jordi Boggiano",
202 | "email": "j.boggiano@seld.be",
203 | "homepage": "http://seld.be",
204 | "role": "Developer"
205 | }
206 | ],
207 | "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
208 | "homepage": "http://github.com/Seldaek/monolog",
209 | "keywords": [
210 | "log",
211 | "logging",
212 | "psr-3"
213 | ],
214 | "time": "2013-11-14 19:48:31"
215 | },
216 | {
217 | "name": "pimple/pimple",
218 | "version": "v1.1.0",
219 | "source": {
220 | "type": "git",
221 | "url": "https://github.com/fabpot/Pimple.git",
222 | "reference": "471c7d7c52ad6594e17b8ec33efdd1be592b5d83"
223 | },
224 | "dist": {
225 | "type": "zip",
226 | "url": "https://api.github.com/repos/fabpot/Pimple/zipball/471c7d7c52ad6594e17b8ec33efdd1be592b5d83",
227 | "reference": "471c7d7c52ad6594e17b8ec33efdd1be592b5d83",
228 | "shasum": ""
229 | },
230 | "require": {
231 | "php": ">=5.3.0"
232 | },
233 | "type": "library",
234 | "extra": {
235 | "branch-alias": {
236 | "dev-master": "1.1.x-dev"
237 | }
238 | },
239 | "autoload": {
240 | "psr-0": {
241 | "Pimple": "lib/"
242 | }
243 | },
244 | "notification-url": "https://packagist.org/downloads/",
245 | "license": [
246 | "MIT"
247 | ],
248 | "authors": [
249 | {
250 | "name": "Fabien Potencier",
251 | "email": "fabien@symfony.com"
252 | }
253 | ],
254 | "description": "Pimple is a simple Dependency Injection Container for PHP 5.3",
255 | "homepage": "http://pimple.sensiolabs.org",
256 | "keywords": [
257 | "container",
258 | "dependency injection"
259 | ],
260 | "time": "2013-09-19 04:53:08"
261 | },
262 | {
263 | "name": "psr/log",
264 | "version": "1.0.0",
265 | "source": {
266 | "type": "git",
267 | "url": "https://github.com/php-fig/log",
268 | "reference": "1.0.0"
269 | },
270 | "dist": {
271 | "type": "zip",
272 | "url": "https://github.com/php-fig/log/archive/1.0.0.zip",
273 | "reference": "1.0.0",
274 | "shasum": ""
275 | },
276 | "type": "library",
277 | "autoload": {
278 | "psr-0": {
279 | "Psr\\Log\\": ""
280 | }
281 | },
282 | "notification-url": "https://packagist.org/downloads/",
283 | "license": [
284 | "MIT"
285 | ],
286 | "authors": [
287 | {
288 | "name": "PHP-FIG",
289 | "homepage": "http://www.php-fig.org/"
290 | }
291 | ],
292 | "description": "Common interface for logging libraries",
293 | "keywords": [
294 | "log",
295 | "psr",
296 | "psr-3"
297 | ],
298 | "time": "2012-12-21 11:40:51"
299 | },
300 | {
301 | "name": "symfony/finder",
302 | "version": "v2.3.7",
303 | "target-dir": "Symfony/Component/Finder",
304 | "source": {
305 | "type": "git",
306 | "url": "https://github.com/symfony/Finder.git",
307 | "reference": "a175521f680b178e63c5d0ab87c6b046c0990c3f"
308 | },
309 | "dist": {
310 | "type": "zip",
311 | "url": "https://api.github.com/repos/symfony/Finder/zipball/a175521f680b178e63c5d0ab87c6b046c0990c3f",
312 | "reference": "a175521f680b178e63c5d0ab87c6b046c0990c3f",
313 | "shasum": ""
314 | },
315 | "require": {
316 | "php": ">=5.3.3"
317 | },
318 | "type": "library",
319 | "extra": {
320 | "branch-alias": {
321 | "dev-master": "2.3-dev"
322 | }
323 | },
324 | "autoload": {
325 | "psr-0": {
326 | "Symfony\\Component\\Finder\\": ""
327 | }
328 | },
329 | "notification-url": "https://packagist.org/downloads/",
330 | "license": [
331 | "MIT"
332 | ],
333 | "authors": [
334 | {
335 | "name": "Fabien Potencier",
336 | "email": "fabien@symfony.com"
337 | },
338 | {
339 | "name": "Symfony Community",
340 | "homepage": "http://symfony.com/contributors"
341 | }
342 | ],
343 | "description": "Symfony Finder Component",
344 | "homepage": "http://symfony.com",
345 | "time": "2013-09-19 09:45:20"
346 | },
347 | {
348 | "name": "symfony/process",
349 | "version": "v2.3.7",
350 | "target-dir": "Symfony/Component/Process",
351 | "source": {
352 | "type": "git",
353 | "url": "https://github.com/symfony/Process.git",
354 | "reference": "82898108f79040314a7b3ba430a72c32c7f61d14"
355 | },
356 | "dist": {
357 | "type": "zip",
358 | "url": "https://api.github.com/repos/symfony/Process/zipball/82898108f79040314a7b3ba430a72c32c7f61d14",
359 | "reference": "82898108f79040314a7b3ba430a72c32c7f61d14",
360 | "shasum": ""
361 | },
362 | "require": {
363 | "php": ">=5.3.3"
364 | },
365 | "type": "library",
366 | "extra": {
367 | "branch-alias": {
368 | "dev-master": "2.3-dev"
369 | }
370 | },
371 | "autoload": {
372 | "psr-0": {
373 | "Symfony\\Component\\Process\\": ""
374 | }
375 | },
376 | "notification-url": "https://packagist.org/downloads/",
377 | "license": [
378 | "MIT"
379 | ],
380 | "authors": [
381 | {
382 | "name": "Fabien Potencier",
383 | "email": "fabien@symfony.com"
384 | },
385 | {
386 | "name": "Symfony Community",
387 | "homepage": "http://symfony.com/contributors"
388 | }
389 | ],
390 | "description": "Symfony Process Component",
391 | "homepage": "http://symfony.com",
392 | "time": "2013-10-30 08:30:20"
393 | }
394 | ],
395 | "packages-dev": [
396 | {
397 | "name": "phpunit/php-code-coverage",
398 | "version": "1.2.13",
399 | "source": {
400 | "type": "git",
401 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
402 | "reference": "466e7cd2554b4e264c9e3f31216d25ac0e5f3d94"
403 | },
404 | "dist": {
405 | "type": "zip",
406 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/466e7cd2554b4e264c9e3f31216d25ac0e5f3d94",
407 | "reference": "466e7cd2554b4e264c9e3f31216d25ac0e5f3d94",
408 | "shasum": ""
409 | },
410 | "require": {
411 | "php": ">=5.3.3",
412 | "phpunit/php-file-iterator": ">=1.3.0@stable",
413 | "phpunit/php-text-template": ">=1.1.1@stable",
414 | "phpunit/php-token-stream": ">=1.1.3@stable"
415 | },
416 | "require-dev": {
417 | "phpunit/phpunit": "3.7.*@dev"
418 | },
419 | "suggest": {
420 | "ext-dom": "*",
421 | "ext-xdebug": ">=2.0.5"
422 | },
423 | "type": "library",
424 | "extra": {
425 | "branch-alias": {
426 | "dev-master": "1.2.x-dev"
427 | }
428 | },
429 | "autoload": {
430 | "classmap": [
431 | "PHP/"
432 | ]
433 | },
434 | "notification-url": "https://packagist.org/downloads/",
435 | "include-path": [
436 | ""
437 | ],
438 | "license": [
439 | "BSD-3-Clause"
440 | ],
441 | "authors": [
442 | {
443 | "name": "Sebastian Bergmann",
444 | "email": "sb@sebastian-bergmann.de",
445 | "role": "lead"
446 | }
447 | ],
448 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
449 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
450 | "keywords": [
451 | "coverage",
452 | "testing",
453 | "xunit"
454 | ],
455 | "time": "2013-09-10 08:14:32"
456 | },
457 | {
458 | "name": "phpunit/php-file-iterator",
459 | "version": "1.3.4",
460 | "source": {
461 | "type": "git",
462 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
463 | "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb"
464 | },
465 | "dist": {
466 | "type": "zip",
467 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/acd690379117b042d1c8af1fafd61bde001bf6bb",
468 | "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb",
469 | "shasum": ""
470 | },
471 | "require": {
472 | "php": ">=5.3.3"
473 | },
474 | "type": "library",
475 | "autoload": {
476 | "classmap": [
477 | "File/"
478 | ]
479 | },
480 | "notification-url": "https://packagist.org/downloads/",
481 | "include-path": [
482 | ""
483 | ],
484 | "license": [
485 | "BSD-3-Clause"
486 | ],
487 | "authors": [
488 | {
489 | "name": "Sebastian Bergmann",
490 | "email": "sb@sebastian-bergmann.de",
491 | "role": "lead"
492 | }
493 | ],
494 | "description": "FilterIterator implementation that filters files based on a list of suffixes.",
495 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
496 | "keywords": [
497 | "filesystem",
498 | "iterator"
499 | ],
500 | "time": "2013-10-10 15:34:57"
501 | },
502 | {
503 | "name": "phpunit/php-text-template",
504 | "version": "1.1.4",
505 | "source": {
506 | "type": "git",
507 | "url": "git://github.com/sebastianbergmann/php-text-template.git",
508 | "reference": "1.1.4"
509 | },
510 | "dist": {
511 | "type": "zip",
512 | "url": "https://github.com/sebastianbergmann/php-text-template/zipball/1.1.4",
513 | "reference": "1.1.4",
514 | "shasum": ""
515 | },
516 | "require": {
517 | "php": ">=5.3.3"
518 | },
519 | "type": "library",
520 | "autoload": {
521 | "classmap": [
522 | "Text/"
523 | ]
524 | },
525 | "notification-url": "https://packagist.org/downloads/",
526 | "include-path": [
527 | ""
528 | ],
529 | "license": [
530 | "BSD-3-Clause"
531 | ],
532 | "authors": [
533 | {
534 | "name": "Sebastian Bergmann",
535 | "email": "sb@sebastian-bergmann.de",
536 | "role": "lead"
537 | }
538 | ],
539 | "description": "Simple template engine.",
540 | "homepage": "https://github.com/sebastianbergmann/php-text-template/",
541 | "keywords": [
542 | "template"
543 | ],
544 | "time": "2012-10-31 11:15:28"
545 | },
546 | {
547 | "name": "phpunit/php-timer",
548 | "version": "1.0.5",
549 | "source": {
550 | "type": "git",
551 | "url": "https://github.com/sebastianbergmann/php-timer.git",
552 | "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c"
553 | },
554 | "dist": {
555 | "type": "zip",
556 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/19689d4354b295ee3d8c54b4f42c3efb69cbc17c",
557 | "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c",
558 | "shasum": ""
559 | },
560 | "require": {
561 | "php": ">=5.3.3"
562 | },
563 | "type": "library",
564 | "autoload": {
565 | "classmap": [
566 | "PHP/"
567 | ]
568 | },
569 | "notification-url": "https://packagist.org/downloads/",
570 | "include-path": [
571 | ""
572 | ],
573 | "license": [
574 | "BSD-3-Clause"
575 | ],
576 | "authors": [
577 | {
578 | "name": "Sebastian Bergmann",
579 | "email": "sb@sebastian-bergmann.de",
580 | "role": "lead"
581 | }
582 | ],
583 | "description": "Utility class for timing",
584 | "homepage": "https://github.com/sebastianbergmann/php-timer/",
585 | "keywords": [
586 | "timer"
587 | ],
588 | "time": "2013-08-02 07:42:54"
589 | },
590 | {
591 | "name": "phpunit/php-token-stream",
592 | "version": "1.2.1",
593 | "source": {
594 | "type": "git",
595 | "url": "https://github.com/sebastianbergmann/php-token-stream.git",
596 | "reference": "5220af2a7929aa35cf663d97c89ad3d50cf5fa3e"
597 | },
598 | "dist": {
599 | "type": "zip",
600 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/5220af2a7929aa35cf663d97c89ad3d50cf5fa3e",
601 | "reference": "5220af2a7929aa35cf663d97c89ad3d50cf5fa3e",
602 | "shasum": ""
603 | },
604 | "require": {
605 | "ext-tokenizer": "*",
606 | "php": ">=5.3.3"
607 | },
608 | "type": "library",
609 | "extra": {
610 | "branch-alias": {
611 | "dev-master": "1.2-dev"
612 | }
613 | },
614 | "autoload": {
615 | "classmap": [
616 | "PHP/"
617 | ]
618 | },
619 | "notification-url": "https://packagist.org/downloads/",
620 | "include-path": [
621 | ""
622 | ],
623 | "license": [
624 | "BSD-3-Clause"
625 | ],
626 | "authors": [
627 | {
628 | "name": "Sebastian Bergmann",
629 | "email": "sb@sebastian-bergmann.de",
630 | "role": "lead"
631 | }
632 | ],
633 | "description": "Wrapper around PHP's tokenizer extension.",
634 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
635 | "keywords": [
636 | "tokenizer"
637 | ],
638 | "time": "2013-09-13 04:58:23"
639 | },
640 | {
641 | "name": "phpunit/phpunit",
642 | "version": "3.7.28",
643 | "source": {
644 | "type": "git",
645 | "url": "https://github.com/sebastianbergmann/phpunit.git",
646 | "reference": "3b97c8492bcafbabe6b6fbd2ab35f2f04d932a8d"
647 | },
648 | "dist": {
649 | "type": "zip",
650 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3b97c8492bcafbabe6b6fbd2ab35f2f04d932a8d",
651 | "reference": "3b97c8492bcafbabe6b6fbd2ab35f2f04d932a8d",
652 | "shasum": ""
653 | },
654 | "require": {
655 | "ext-dom": "*",
656 | "ext-pcre": "*",
657 | "ext-reflection": "*",
658 | "ext-spl": "*",
659 | "php": ">=5.3.3",
660 | "phpunit/php-code-coverage": "~1.2.1",
661 | "phpunit/php-file-iterator": ">=1.3.1",
662 | "phpunit/php-text-template": ">=1.1.1",
663 | "phpunit/php-timer": ">=1.0.4",
664 | "phpunit/phpunit-mock-objects": "~1.2.0",
665 | "symfony/yaml": "~2.0"
666 | },
667 | "require-dev": {
668 | "pear-pear/pear": "1.9.4"
669 | },
670 | "suggest": {
671 | "ext-json": "*",
672 | "ext-simplexml": "*",
673 | "ext-tokenizer": "*",
674 | "phpunit/php-invoker": ">=1.1.0,<1.2.0"
675 | },
676 | "bin": [
677 | "composer/bin/phpunit"
678 | ],
679 | "type": "library",
680 | "extra": {
681 | "branch-alias": {
682 | "dev-master": "3.7.x-dev"
683 | }
684 | },
685 | "autoload": {
686 | "classmap": [
687 | "PHPUnit/"
688 | ]
689 | },
690 | "notification-url": "https://packagist.org/downloads/",
691 | "include-path": [
692 | "",
693 | "../../symfony/yaml/"
694 | ],
695 | "license": [
696 | "BSD-3-Clause"
697 | ],
698 | "authors": [
699 | {
700 | "name": "Sebastian Bergmann",
701 | "email": "sebastian@phpunit.de",
702 | "role": "lead"
703 | }
704 | ],
705 | "description": "The PHP Unit Testing framework.",
706 | "homepage": "http://www.phpunit.de/",
707 | "keywords": [
708 | "phpunit",
709 | "testing",
710 | "xunit"
711 | ],
712 | "time": "2013-10-17 07:27:40"
713 | },
714 | {
715 | "name": "phpunit/phpunit-mock-objects",
716 | "version": "1.2.3",
717 | "source": {
718 | "type": "git",
719 | "url": "git://github.com/sebastianbergmann/phpunit-mock-objects.git",
720 | "reference": "1.2.3"
721 | },
722 | "dist": {
723 | "type": "zip",
724 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects/archive/1.2.3.zip",
725 | "reference": "1.2.3",
726 | "shasum": ""
727 | },
728 | "require": {
729 | "php": ">=5.3.3",
730 | "phpunit/php-text-template": ">=1.1.1@stable"
731 | },
732 | "suggest": {
733 | "ext-soap": "*"
734 | },
735 | "type": "library",
736 | "autoload": {
737 | "classmap": [
738 | "PHPUnit/"
739 | ]
740 | },
741 | "notification-url": "https://packagist.org/downloads/",
742 | "include-path": [
743 | ""
744 | ],
745 | "license": [
746 | "BSD-3-Clause"
747 | ],
748 | "authors": [
749 | {
750 | "name": "Sebastian Bergmann",
751 | "email": "sb@sebastian-bergmann.de",
752 | "role": "lead"
753 | }
754 | ],
755 | "description": "Mock Object library for PHPUnit",
756 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
757 | "keywords": [
758 | "mock",
759 | "xunit"
760 | ],
761 | "time": "2013-01-13 10:24:48"
762 | },
763 | {
764 | "name": "symfony/yaml",
765 | "version": "v2.3.7",
766 | "target-dir": "Symfony/Component/Yaml",
767 | "source": {
768 | "type": "git",
769 | "url": "https://github.com/symfony/Yaml.git",
770 | "reference": "c1bda5b459d792cb253de12c65beba3040163b2b"
771 | },
772 | "dist": {
773 | "type": "zip",
774 | "url": "https://api.github.com/repos/symfony/Yaml/zipball/c1bda5b459d792cb253de12c65beba3040163b2b",
775 | "reference": "c1bda5b459d792cb253de12c65beba3040163b2b",
776 | "shasum": ""
777 | },
778 | "require": {
779 | "php": ">=5.3.3"
780 | },
781 | "type": "library",
782 | "extra": {
783 | "branch-alias": {
784 | "dev-master": "2.3-dev"
785 | }
786 | },
787 | "autoload": {
788 | "psr-0": {
789 | "Symfony\\Component\\Yaml\\": ""
790 | }
791 | },
792 | "notification-url": "https://packagist.org/downloads/",
793 | "license": [
794 | "MIT"
795 | ],
796 | "authors": [
797 | {
798 | "name": "Fabien Potencier",
799 | "email": "fabien@symfony.com"
800 | },
801 | {
802 | "name": "Symfony Community",
803 | "homepage": "http://symfony.com/contributors"
804 | }
805 | ],
806 | "description": "Symfony Yaml Component",
807 | "homepage": "http://symfony.com",
808 | "time": "2013-10-17 11:48:01"
809 | }
810 | ],
811 | "aliases": [
812 |
813 | ],
814 | "minimum-stability": "stable",
815 | "stability-flags": [
816 |
817 | ],
818 | "platform": {
819 | "php": ">=5.3.3"
820 | },
821 | "platform-dev": [
822 |
823 | ]
824 | }
825 |
--------------------------------------------------------------------------------