Gunk up your build pipeline with Pulp
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
bin
src/Pulp
tests
.gitignore
README.md
build-phar.php
composer.json

README.md

Install

Download the phar file, make it executable and move it to /usr/local/bin/pulp

wget https://github.com/Pulp-tool/pulp-cli/releases/download/0.15.0/pulp.phar
chmod a+x pulp.phar
mv pulp.phar /usr/local/bin/pulp

Initialize your project

From your project root:

pulp init-project
git add .pulp/
git commit -m "Adding pulp build tool"
cd .pulp
composer install

Use

Create tasks and watchers, pipe data from src() to dest()

$p = new \Pulp\Pulp();

//The log task is a dependency of the build task.
//This will run the log task before running the build task

$p->task('build', ['log'], function() {
	echo "Building...\n";
});


//The log task is a regular closure

$foo = time();
$p->task('log', function() use ($foo) {
	echo "Logging with variable: $foo...\n";
});

//The watch method will either poll or use inotify to detect file changes

$p->task('wait-and-pipe', function() use($p) {
	$p->watch( ['src/**/*.php', 'foo/**/*.php'])->on('change', function($file) use ($p) {
		//this will pipe *all* files in src/ and foo/, not just the ones that changed
		$p->src(['src/', 'foo/'])
			->pipe(new \Pulp\DataPipe(function($data, $pipe) {
				echo "Data Pipe got "; var_dump($data);
				$pipe->push($data); //propagate data down the chain
			}))
			->pipe(new \Pulp\DataPipe(null, function($data, $pipe) {
				// the second parameter is for end even handlers
				echo "I'm only called once \n";
			})

		);
			
		echo "This file triggered the change event: ".$file." ...\n";
	});
});
pulp build
pulp watch

Compile Less


$p = new \Pulp\Pulp();

function compileLess($p) {
	return $p->src(['foo/bootstrap.less'])
	  ->pipe(new \Pulp\Debug())                     //will simply print every file from src
	  ->pipe(new \Pulp\Less( ['compress'=>TRUE] ))  //renames files from *.less to *.css
	  ->pipe($p->dest('foo/'));                     //will output foo/bootstrap.css

}
$p->task('less-now', function() use($p) {
	compileLess($p)
});

$p->task('less', function() use($p) {
	compileLess($p);  //start by refreshing once
	$p->watch( ['foo/**/*.less'])->on('change', function($file) use ($p, $lr) {
		compileLess($p);
	});
});
[11:26:25] Starting task 'less'
[11:26:25] Finished task 'less' (took: 9.230 ms)
[11:26:25] pulp-debug: bootstrap.less
[11:26:29] pulp-debug: bootstrap.less

Compile Less with LiveReload


$p = new \Pulp\Pulp();
$lr = new \Pulp\LiveReload();

function compileLess($p) {
	return $p->src(['foo/bootstrap.less'])
	  ->pipe(new \Pulp\Debug())                     //will simply print every file from src
	  ->pipe(new \Pulp\Less( ['compress'=>TRUE] ))  //renames files from *.less to *.css
	  ->pipe($p->dest('foo/'));                     //will output foo/bootstrap.css

}

$p->task('less', function() use($p) {
	$lr = new lr();
	$lr->listen($p->loop);

	compileLess($p);  //start by refreshing once
	$p->watch( ['foo/**/*.less'])->on('change', function($file) use ($p, $lr) {
		$stream = compileLess($p);
		return $stream->pipe($lr);
	});
});
[11:27:11] Starting task 'less'
[11:27:11] Finished task 'less' (took: 9.230 ms)
[11:27:11] pulp-debug: bootstrap.less
[11:27:15] pulp-debug: bootstrap.less
[11:27:15] Sending reload because file changed: bootstrap.css

Compile your own Phar


$pharSettings = [
	'alias'      => 'myscript-'.time().'.phar',
	'srcRoot'    => "src/**",
	'vendorRoot' => "vendor/**",
	'buildRoot'  => "build/",
	'pharFile'   => "build/myscript.phar",
	'entryPoint' => 'bin/myscript.php',
];

$p->task('clean-phar', function() use($pharSettings, $p) {
	$pharFile = $pharSettings['pharFile'];
	return @exec("rm ". $pharFile);
});

$p->task('build-phar', ['clean-phar'], function() use($p, $pharSettings) {
	$pharFile  = $pharSettings['pharFile'];
	$pharAlias = $pharSettings['alias'];
	$buildRoot = $pharSettings['buildRoot'];
	$count     = 0;

	@exec("mkdir ".$buildRoot);

	$phar = new Phar($pharFile,
		FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::KEY_AS_FILENAME,
		$pharAlias
	);

	$phar->startBuffering();

	$dirList = [ $pharSettings['srcRoot'], $pharSettings['vendorRoot'] ];
	return $p->src($dirList)
		->pipe(new \Pulp\DataPipe(function($file, $pipe) use(&$count, $phar) {
			$count++;
			$pipe->emit('log', ["Adding to phar build: ".$file->getPathName()]);
			$phar->addFromString($file->getPathname(), file_get_contents($file->getPathname()));
			$pipe->push($file);
		}))
		->pipe(new \Pulp\DataPipe(NULL, function($pipe) use(&$count, $phar, $pharFile, $pharStub) {
			$pipe->log("Packaging %d files.", [$count]);

			//pack the main bin file
			$content = file_get_contents($pharSettings['entryPoint']);
			$content = preg_replace('{^#!/usr/bin/env php\s*}', '', $content);
			$phar->addFromString($pharSettings['entryPoint'], $content);

			$phar->createDefaultStub($pharSettings['entryPoint']);
			$phar->stopBuffering();

			$pipe->log("Generated <file>$pharFile</>");

			unset($phar);
			chmod($pharFile, 0755);
			$pipe->log("File size is ".round(filesize($pharFile) / 1024 / 1024, 2)." MB.");
		}));
});
[12:06:09] Starting task 'clean-phar'
[12:06:09] Finished task 'clean-phar' (took: 2.671 ms)
[12:06:09] Starting task 'build-phar'
[12:06:09] Adding to phar build: vendor/autoload.php
[12:06:09] Adding to phar build: vendor/react/stream/CHANGELOG.md
[12:06:09] Adding to phar build: vendor/react/stream/src/ThroughStream.php
[12:06:09] Adding to phar build: vendor/react/stream/src/CompositeStream.php
[12:06:09] Adding to phar build: vendor/react/stream/src/DuplexResourceStream.php
[12:06:09] Adding to phar build: vendor/react/stream/src/ReadableStreamInterface.php 
[12:06:09] Adding to phar build: vendor/react/stream/src/WritableResourceStream.php
[12:06:09] Adding to phar build: src/your-src-files-here.php
...
[12:06:09] Packaging 50 files.
[12:06:09] Generated build/myscript.phar
[12:06:09] File size is 0.16 MB.
[12:06:09] Finished task 'build-phar' (took: 34.887 ms)

Setup your project

Run pulp init-project to create a .pulp directory, a config.php file and a composer.json file.

your-project/
  .pulp/            # <== created with pulp init-project
     config.php     # <== tasks go here
     composer.json  # <== pulp plugins go here
  src/
    controllers/
  public/
    templates/
      styles/
        my-project.less

CD into the .pulp directory and run composer install

Edit config.php and create your own build pipeline

$p Pulp object

method
src( array [$srcGlob [, $srcGlob, ...]], callback fn($file) )
A string or array of strings that point to source files. Globbing with ?, * and ** is supported. eg: ["src/**/*.php", "vendor/**/*"]. The callback fn() will accept one $file parameter.
watch( array [$srcGlob [, $srcGlob, ...]] )
A string or array of strings that point to source files. Globbing with ?, * and ** is supported. eg: ["src/**/*.php", "vendor/**/*"]. This will return an object that will emit a "change" event whenever a matching file changes.
dest( [$destDir, [$destDir, ...]] )
A string or array of destination directories (not globs). This is usually the end of a pipeline and does not have a callback function.
task( $name, optional [dependencies], callback fn($p) )
A simple string name to call from command line, optional list of dependent tasks to run before this task starts and a callback function that will receive the $p object.

DataPipe object

method
pipe( WritableStreamInterface $destinationPipe [, array $options] )
The pipe method can accept a new DataPipe object or any object that implements WritableStreamInterface.
__construct( callback fn($file, $pipe) [, callback fn($pipe) ])
The first argument is a callback that is called on every "data" event from the source pipe. The second argument is a callback that is called on the "end" event from the source pipe. Only one callback is required. Note that the end callback does not receive a $file parameter.