├── .gitignore
├── tests
├── Basset
│ ├── Manifest
│ │ ├── fixtures
│ │ │ └── collections.json
│ │ ├── ManifestTest.php
│ │ └── EntryTest.php
│ ├── Filter
│ │ ├── UriRewriteFilterTest.php
│ │ ├── CssoFilterTest.php
│ │ └── FilterTest.php
│ ├── Factory
│ │ ├── FilterFactoryTest.php
│ │ └── AssetFactoryTest.php
│ ├── EnvironmentTest.php
│ ├── Builder
│ │ ├── FilesystemCleanerTest.php
│ │ └── BuilderTest.php
│ ├── CollectionTest.php
│ ├── AssetFinderTest.php
│ ├── AssetTest.php
│ ├── ServerTest.php
│ └── DirectoryTest.php
└── Cases
│ └── FilterTestCase.php
├── src
├── Basset
│ ├── Exceptions
│ │ ├── AssetNotFoundException.php
│ │ ├── BuildNotRequiredException.php
│ │ └── DirectoryNotFoundException.php
│ ├── Facade.php
│ ├── Factory
│ │ ├── Factory.php
│ │ ├── FactoryManager.php
│ │ ├── FilterFactory.php
│ │ └── AssetFactory.php
│ ├── Filter
│ │ ├── CssoFilter.php
│ │ ├── Filterable.php
│ │ └── UriRewriteFilter.php
│ ├── Console
│ │ ├── BassetCommand.php
│ │ └── BuildCommand.php
│ ├── Manifest
│ │ ├── Manifest.php
│ │ └── Entry.php
│ ├── Collection.php
│ ├── Environment.php
│ ├── Builder
│ │ ├── FilesystemCleaner.php
│ │ └── Builder.php
│ ├── AssetFinder.php
│ ├── BassetServiceProvider.php
│ ├── Server.php
│ ├── Asset.php
│ └── Directory.php
├── helpers.php
└── config
│ └── config.php
├── .travis.yml
├── phpunit.xml
├── composer.json
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | /coverage
3 | composer.phar
4 | composer.lock
5 | .DS_Store
6 | *.sublime-*
--------------------------------------------------------------------------------
/tests/Basset/Manifest/fixtures/collections.json:
--------------------------------------------------------------------------------
1 | {"foo":{"fingerprints":{"stylesheets":"bar"},"development":{"stylesheets":{"baz/qux.scss":"baz/qux.css"}}}}
--------------------------------------------------------------------------------
/src/Basset/Exceptions/AssetNotFoundException.php:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 | ./tests/
16 |
17 |
18 |
--------------------------------------------------------------------------------
/tests/Cases/FilterTestCase.php:
--------------------------------------------------------------------------------
1 | find($name);
26 | }
27 |
28 |
29 | }
--------------------------------------------------------------------------------
/src/Basset/Factory/Factory.php:
--------------------------------------------------------------------------------
1 | log = $log;
30 |
31 | return $this;
32 | }
33 |
34 | /**
35 | * Set the factory manager instance.
36 | *
37 | * @param \Basset\Factory\FactoryManager $factory
38 | * @return \Basset\Factory\Factory
39 | */
40 | public function setFactoryManager(FactoryManager $factory)
41 | {
42 | $this->factory = $factory;
43 |
44 | return $this;
45 | }
46 |
47 | }
--------------------------------------------------------------------------------
/tests/Basset/Filter/UriRewriteFilterTest.php:
--------------------------------------------------------------------------------
1 | load();
17 |
18 | $filter->filterDump($asset);
19 |
20 | $this->assertEquals("body { background-image: url('/foo/bar.png'); }", $asset->getContent());
21 | }
22 |
23 |
24 | public function testUriRewriteWithSymlinks()
25 | {
26 | $filter = new UriRewriteFilter('path/to/public', array('//assets' => strtr('path/to/outside/public/assets', '/', DIRECTORY_SEPARATOR)));
27 |
28 | $input = "body { background-image: url('../foo/bar.png'); }";
29 |
30 | $asset = new StringAsset($input, array(), 'path/to/outside/public/assets/baz', 'qux.css');
31 | $asset->load();
32 |
33 | $filter->filterDump($asset);
34 |
35 | $this->assertEquals("body { background-image: url('/assets/foo/bar.png'); }", $asset->getContent());
36 | }
37 |
38 |
39 | }
--------------------------------------------------------------------------------
/tests/Basset/Filter/CssoFilterTest.php:
--------------------------------------------------------------------------------
1 | findExecutable('csso', 'CSSO_BIN');
16 | $nodeBin = $this->findExecutable('node', 'NODE_BIN');
17 |
18 | if ( ! $cssoBin or ! $nodeBin)
19 | {
20 | $this->markTestIncomplete('Could not find CSSO or Node executables.');
21 | }
22 |
23 | $this->filter = new CssoFilter($cssoBin, $nodeBin);
24 | }
25 |
26 |
27 | public function tearDown()
28 | {
29 | $this->filter = null;
30 | }
31 |
32 |
33 | public function testCsso()
34 | {
35 | $input = '.test { height: 10px; height: 20px; }';
36 |
37 | $asset = new StringAsset($input);
38 | $asset->load();
39 |
40 | try
41 | {
42 | $this->filter->filterLoad($asset);
43 | }
44 | catch (FilterException $e)
45 | {
46 | $this->markTestIncomplete('Could not properly test CSSO filter. Make sure Node and CSSO are in your PATH.');
47 | }
48 |
49 | $this->assertEquals('.test{height:20px}', $asset->getContent());
50 | }
51 |
52 |
53 | }
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jasonlewis/basset",
3 | "description": "A better asset management package for Laravel.",
4 | "keywords": ["assets", "basset", "laravel"],
5 | "license": "BSD-2-Clause",
6 | "authors": [
7 | {
8 | "name": "Jason Lewis",
9 | "email": "jason.lewis1991@gmail.com"
10 | }
11 | ],
12 | "require": {
13 | "php": ">=5.3.0",
14 | "kriswallsmith/assetic": "1.1.*"
15 | },
16 | "require-dev": {
17 | "mockery/mockery": ">=0.7.2",
18 | "illuminate/config": "4.0.*",
19 | "illuminate/console": "4.0.*",
20 | "illuminate/filesystem": "4.0.*",
21 | "illuminate/log": "4.0.*",
22 | "illuminate/routing": "4.0.*",
23 | "illuminate/support": "4.0.*",
24 | "symfony/process": "2.3.*"
25 | },
26 | "suggest": {
27 | "aws/aws-sdk-php": "Deploy static assets directly to your S3 buckets.",
28 | "rackspace/php-cloudfiles": "Deploy static assets directly to your Cloud Files container."
29 | },
30 | "autoload": {
31 | "psr-0": {
32 | "Basset": "src/"
33 | },
34 | "classmap": [
35 | "tests/Cases/FilterTestCase.php"
36 | ],
37 | "files": ["src/helpers.php"]
38 | },
39 | "extra": {
40 | "branch-alias": {
41 | "dev-master": "4.0-dev"
42 | }
43 | },
44 | "minimum-stability": "dev"
45 | }
--------------------------------------------------------------------------------
/tests/Basset/Factory/FilterFactoryTest.php:
--------------------------------------------------------------------------------
1 | factory = new Basset\Factory\FilterFactory(array('foo' => 'FooFilter', 'bar' => array('BarFilter', function($filter)
17 | {
18 | $filter->setArgument('foo');
19 | })), array(), 'testing');
20 |
21 | $this->factory->setLogger(m::mock('Illuminate\Log\Writer'));
22 | }
23 |
24 |
25 | public function testMakeNewFilterInstanceFromString()
26 | {
27 | $this->assertInstanceOf('Basset\Filter\Filter', $this->factory->make('FooFilter'));
28 | }
29 |
30 |
31 | public function testMakeFilterInstanceFromExistingFilterInstance()
32 | {
33 | $filter = m::mock('Basset\Filter\Filter');
34 | $this->assertEquals($filter, $this->factory->make($filter));
35 | }
36 |
37 |
38 | public function testMakeFromConfigAlias()
39 | {
40 | $filter = $this->factory->make('foo');
41 | $this->assertEquals('FooFilter', $filter->getFilter());
42 | }
43 |
44 |
45 | public function testMakeFromConfigAliasWithCallback()
46 | {
47 | $filter = $this->factory->make('bar');
48 | $this->assertContains('foo', $filter->getArguments());
49 | }
50 |
51 |
52 | }
--------------------------------------------------------------------------------
/src/Basset/Factory/FactoryManager.php:
--------------------------------------------------------------------------------
1 | driver($factory);
16 | }
17 |
18 | /**
19 | * Create the asset factory driver.
20 | *
21 | * @return \Basset\Factory\AssetFactory
22 | */
23 | public function createAssetDriver()
24 | {
25 | $asset = new AssetFactory($this->app['files'], $this->app['env'], $this->app['path.public']);
26 |
27 | return $this->factory($asset);
28 | }
29 |
30 | /**
31 | * Create the filter factory driver.
32 | *
33 | * @return \Basset\Factory\FilterFactory
34 | */
35 | public function createFilterDriver()
36 | {
37 | $aliases = $this->app['config']->get('basset::aliases.filters', array());
38 |
39 | $node = $this->app['config']->get('basset::node_paths', array());
40 |
41 | $filter = new FilterFactory($aliases, $node, $this->app['env']);
42 |
43 | return $this->factory($filter);
44 | }
45 |
46 | /**
47 | * Set the logger and factory manager on the factory instance.
48 | *
49 | * @param \Basset\Factory\Factory $factory
50 | * @return \Basset\Factory\Factory
51 | */
52 | protected function factory(Factory $factory)
53 | {
54 | $factory->setLogger($this->getLogger());
55 |
56 | return $factory->setFactoryManager($this);
57 | }
58 |
59 | /**
60 | * Get the log writer instance.
61 | *
62 | * @return \Illuminate\Log\Writer
63 | */
64 | public function getLogger()
65 | {
66 | return $this->app['basset.log'];
67 | }
68 |
69 | }
--------------------------------------------------------------------------------
/tests/Basset/Factory/AssetFactoryTest.php:
--------------------------------------------------------------------------------
1 | files = m::mock('Illuminate\Filesystem\Filesystem');
17 | $this->asset = new Basset\Factory\AssetFactory($this->files, 'testing', __DIR__);
18 |
19 | $this->asset->setLogger($this->log = m::mock('Illuminate\Log\Writer'));
20 | $this->asset->setFactoryManager(m::mock('Basset\Factory\FactoryManager'));
21 | }
22 |
23 |
24 | public function testMakeAsset()
25 | {
26 | $asset = $this->asset->make(__FILE__);
27 |
28 | $this->assertEquals(basename(__FILE__), $asset->getRelativePath());
29 | $this->assertEquals(__FILE__, $asset->getAbsolutePath());
30 | }
31 |
32 |
33 | public function testBuildingOfAbsolutePath()
34 | {
35 | $this->assertEquals(__FILE__, $this->asset->buildAbsolutePath(__FILE__));
36 | $this->assertEquals('http://foo.com', $this->asset->buildAbsolutePath('http://foo.com'));
37 | $this->assertEquals('//foo.com', $this->asset->buildAbsolutePath('//foo.com'));
38 | }
39 |
40 | public function testBuildingOfRelativePath()
41 | {
42 | $this->assertEquals('foo.css', $this->asset->buildRelativePath(__DIR__.'/foo.css'));
43 | $this->assertEquals('bar/foo.css', $this->asset->buildRelativePath(__DIR__.'/bar/foo.css'));
44 | $this->assertEquals('http://foo.com', $this->asset->buildRelativePath('http://foo.com'));
45 | }
46 |
47 |
48 | public function testBuildingOfRelativePathFromOutsidePublicDirectory()
49 | {
50 | $this->assertEquals(md5('path/to/outside').'/foo.css', $this->asset->buildRelativePath('path/to/outside/foo.css'));
51 | $this->assertEquals(md5('path/to').'/bar.css', $this->asset->buildRelativePath('path/to/bar.css'));
52 | }
53 |
54 |
55 | }
--------------------------------------------------------------------------------
/src/Basset/Factory/FilterFactory.php:
--------------------------------------------------------------------------------
1 | aliases = $aliases;
40 | $this->nodePaths = $nodePaths;
41 | $this->appEnvironment = $appEnvironment;
42 | }
43 |
44 | /**
45 | * Make a new filter instance.
46 | *
47 | * @param string|\Basset\Filter\Filter $filter
48 | * @return \Basset\Filter\Filter
49 | */
50 | public function make($filter)
51 | {
52 | if ($filter instanceof Filter)
53 | {
54 | return $filter;
55 | }
56 |
57 | $filter = isset($this->aliases[$filter]) ? $this->aliases[$filter] : $filter;
58 |
59 | if (is_array($filter))
60 | {
61 | list($filter, $callback) = array(current($filter), next($filter));
62 | }
63 |
64 | // If the filter was aliased and the value of the array was a callable closure then
65 | // we'll return and fire the callback on the filter instance so that any arguments
66 | // can be set for the filters constructor.
67 | $filter = new Filter($this->log, $filter, $this->nodePaths, $this->appEnvironment);
68 |
69 | if (isset($callback) and is_callable($callback))
70 | {
71 | call_user_func($callback, $filter);
72 | }
73 |
74 | return $filter;
75 | }
76 |
77 | }
--------------------------------------------------------------------------------
/tests/Basset/EnvironmentTest.php:
--------------------------------------------------------------------------------
1 | finder = m::mock('Basset\AssetFinder');
18 | $this->finder->shouldReceive('setWorkingDirectory')->with('/')->andReturn('/');
19 |
20 | $this->environment = new Environment(m::mock('Basset\Factory\FactoryManager'), $this->finder);
21 | }
22 |
23 |
24 | public function testMakingNewCollectionReturnsNewCollectionInstance()
25 | {
26 | $this->assertInstanceOf('Basset\Collection', $this->environment->collection('foo'));
27 | }
28 |
29 |
30 | public function testMakingNewCollectionFiresCallback()
31 | {
32 | $fired = false;
33 |
34 | $this->environment->collection('foo', function() use (&$fired) { $fired = true; });
35 | $this->assertTrue($fired);
36 | }
37 |
38 |
39 | public function testRegisterPackageNamespaceAndVendorWithEnvironmentAndFinder()
40 | {
41 | $this->finder->shouldReceive('addNamespace')->once()->with('bar', 'foo/bar');
42 | $this->environment->package('foo/bar', 'bar');
43 | }
44 |
45 |
46 | public function testRegisterPackageNamespaceAndVendorWithEnvironmentAndFinderAndGuessNamespace()
47 | {
48 | $this->finder->shouldReceive('addNamespace')->once()->with('bar', 'foo/bar');
49 | $this->environment->package('foo/bar');
50 | }
51 |
52 |
53 | public function testRegisteringArrayOfCollections()
54 | {
55 | $this->environment->collections(array(
56 | 'foo' => function(){},
57 | 'bar' => function(){}
58 | ));
59 | $this->assertCount(2, $this->environment->all());
60 | $this->assertArrayHasKey('foo', $this->environment->all());
61 | }
62 |
63 |
64 | public function testCheckingIfEnvironmentHasCollection()
65 | {
66 | $this->assertFalse($this->environment->has('foo'));
67 | $this->environment->collection('foo');
68 | $this->assertTrue($this->environment->has('foo'));
69 | }
70 |
71 |
72 | }
--------------------------------------------------------------------------------
/src/helpers.php:
--------------------------------------------------------------------------------
1 | all() as $identifier => $collection) $args[] = array("{$identifier}.css", "{$identifier}.js");
53 | }
54 |
55 | array_walk_recursive($args, function($v, $k) use (&$collections) { is_numeric($k) ? ($collections[$v] = null) : ($collections[$k] = $v); });
56 |
57 | foreach ($collections as $collection => $format) $responses[] = app('basset.server')->collection($collection, $format);
58 |
59 | return array_to_newlines($responses);
60 | }
61 | }
62 |
63 | if ( ! function_exists('array_to_newlines'))
64 | {
65 | /**
66 | * Convert an array to a newline separated string.
67 | *
68 | * @param array $array
69 | * @return string
70 | */
71 | function array_to_newlines(array $array)
72 | {
73 | return implode(PHP_EOL, $array);
74 | }
75 | }
--------------------------------------------------------------------------------
/src/Basset/Filter/CssoFilter.php:
--------------------------------------------------------------------------------
1 | cssoBin = $cssoBin;
33 | $this->nodeBin = $nodeBin;
34 | }
35 |
36 | /**
37 | * Apply filter on file load.
38 | *
39 | * @param \Assetic\Asset\AssetInterface $asset
40 | * @return void
41 | */
42 | public function filterLoad(AssetInterface $asset)
43 | {
44 | $inputFile = tempnam(sys_get_temp_dir(), 'csso');
45 |
46 | file_put_contents($inputFile, $asset->getContent());
47 |
48 | // Before we create our process builder we'll create the arguments to be given to the builder.
49 | // If we have a node bin supplied then we'll shift that to the beginning of the array.
50 | $builderArguments = array($this->cssoBin);
51 |
52 | if ( ! is_null($this->nodeBin))
53 | {
54 | array_unshift($builderArguments, $this->nodeBin);
55 | }
56 |
57 | $builder = $this->createProcessBuilder($builderArguments);
58 |
59 | $builder->add($inputFile);
60 |
61 | // Get the process from the builder and run the process.
62 | $process = $builder->getProcess();
63 |
64 | $code = $process->run();
65 |
66 | unlink($inputFile);
67 |
68 | if ($code !== 0)
69 | {
70 | throw FilterException::fromProcess($process)->setInput($asset->getContent());
71 | }
72 |
73 | $asset->setContent($process->getOutput());
74 | }
75 |
76 | /**
77 | * Apply a filter on file dump.
78 | *
79 | * @param \Assetic\Asset\AssetInterface $asset
80 | * @return void
81 | */
82 | public function filterDump(AssetInterface $asset){}
83 |
84 | }
--------------------------------------------------------------------------------
/src/Basset/Filter/Filterable.php:
--------------------------------------------------------------------------------
1 | filters = new \Illuminate\Support\Collection;
22 | }
23 |
24 | /**
25 | * Syntatical sugar for chaining filters.
26 | *
27 | * @param string|array $filter
28 | * @param \Closure $callback
29 | * @return \Basset\Filter\Filter|\Basset\Filter\Filterable
30 | */
31 | public function andApply($filter, Closure $callback = null)
32 | {
33 | return $this->apply($filter, $callback);
34 | }
35 |
36 | /**
37 | * Apply a filter.
38 | *
39 | * @param string|array $filter
40 | * @param \Closure $callback
41 | * @return \Basset\Filter\Filter|\Basset\Filter\Filterable
42 | */
43 | public function apply($filter, Closure $callback = null)
44 | {
45 | // If the supplied filter is an array then we'll treat it as an array of filters that are
46 | // to be applied to the resource.
47 | if (is_array($filter))
48 | {
49 | return $this->applyFromArray($filter);
50 | }
51 |
52 | $filter = $this->factory->get('filter')->make($filter)->setResource($this);
53 |
54 | is_callable($callback) and call_user_func($callback, $filter);
55 |
56 | return $this->filters[$filter->getFilter()] = $filter;
57 | }
58 |
59 | /**
60 | * Apply filter from an array of filters.
61 | *
62 | * @param array $filters
63 | * @return \Basset\Filter\Filterable
64 | */
65 | public function applyFromArray($filters)
66 | {
67 | foreach ($filters as $key => $value)
68 | {
69 | $filter = $this->factory->get('filter')->make(is_callable($value) ? $key : $value)->setResource($this);
70 |
71 | is_callable($value) and call_user_func($value, $filter);
72 |
73 | $this->filters[$filter->getFilter()] = $filter;
74 | }
75 |
76 | return $this;
77 | }
78 |
79 | /**
80 | * Get the applied filters.
81 | *
82 | * @return \Illuminate\Support\Collection
83 | */
84 | public function getFilters()
85 | {
86 | return $this->filters;
87 | }
88 |
89 | /**
90 | * Get the log writer instance.
91 | *
92 | * @return \Illumiante\Log\Writer
93 | */
94 | public function getLogger()
95 | {
96 | return $this->factory->getLogger();
97 | }
98 |
99 | }
--------------------------------------------------------------------------------
/tests/Basset/Manifest/ManifestTest.php:
--------------------------------------------------------------------------------
1 | files = new Illuminate\Filesystem\Filesystem;
17 | $this->manifest = new Basset\Manifest\Manifest($this->files, __DIR__.'/fixtures');
18 | }
19 |
20 |
21 | public function testManifestIsLoadedCorrectlyFromFilesystem()
22 | {
23 | $this->manifest->load();
24 |
25 | $this->assertEquals(array(
26 | 'fingerprints' => array('stylesheets' => 'bar'),
27 | 'development' => array('stylesheets' => array('baz/qux.scss' => 'baz/qux.css'))
28 | ), $this->manifest->get('foo')->toArray());
29 | }
30 |
31 |
32 | public function testGetInvalidManifestEntryReturnsNull()
33 | {
34 | $this->assertNull($this->manifest->get('foo'));
35 | }
36 |
37 |
38 | public function testMakeManifestEntryReturnsNewEntry()
39 | {
40 | $this->assertInstanceOf('Basset\Manifest\Entry', $this->manifest->make('foo'));
41 | }
42 |
43 |
44 | public function testMakeReturnsExistingManifestEntryIfEntryAlreadyExists()
45 | {
46 | $foo = $this->manifest->make('foo');
47 | $this->assertEquals($foo, $this->manifest->make('foo'));
48 | }
49 |
50 |
51 | public function testManifestChecksForExistingEntry()
52 | {
53 | $this->assertFalse($this->manifest->has('foo'));
54 | $this->manifest->make('foo');
55 | $this->assertTrue($this->manifest->has('foo'));
56 | }
57 |
58 |
59 | public function testManifestUsesCollectionInstanceToGetEntryName()
60 | {
61 | $collection = m::mock('Basset\Collection');
62 | $collection->shouldReceive('getIdentifier')->once()->andReturn('foo');
63 | $this->assertInstanceOf('Basset\Manifest\Entry', $this->manifest->make($collection));
64 | }
65 |
66 |
67 | public function testWritesChangedEntriesToManifest()
68 | {
69 | $this->files = m::mock('Illuminate\Filesystem\Filesystem');
70 | $this->manifest = new Basset\Manifest\Manifest($this->files, __DIR__.'/fixtures');
71 |
72 | $entry = $this->manifest->make('foo');
73 | $entry->setProductionFingerprint('stylesheets', 'foo-123.css');
74 |
75 | $this->files->shouldReceive('put')->once()->with(__DIR__.'/fixtures/collections.json', json_encode(array('foo' => $entry->toArray())))->andReturn(true);
76 |
77 | $this->assertTrue($this->manifest->save());
78 | }
79 |
80 |
81 | public function testNonDirtyManifestDoesNotSave()
82 | {
83 | $this->files = m::mock('Illuminate\Filesystem\Filesystem');
84 | $this->manifest = new Basset\Manifest\Manifest($this->files, __DIR__.'/fixtures');
85 |
86 | $this->assertFalse($this->manifest->save());
87 | }
88 |
89 |
90 | }
--------------------------------------------------------------------------------
/src/Basset/Factory/AssetFactory.php:
--------------------------------------------------------------------------------
1 | files = $files;
47 | $this->appEnvironment = $appEnvironment;
48 | $this->publicPath = $publicPath;
49 | }
50 |
51 | /**
52 | * Make a new asset instance.
53 | *
54 | * @param string $path
55 | * @return \Basset\Asset
56 | */
57 | public function make($path)
58 | {
59 | $absolutePath = $this->buildAbsolutePath($path);
60 |
61 | $relativePath = $this->buildRelativePath($absolutePath);
62 |
63 | $asset = new Asset($this->files, $this->factory, $this->appEnvironment, $absolutePath, $relativePath);
64 |
65 | return $asset->setOrder($this->nextAssetOrder());
66 | }
67 |
68 | /**
69 | * Build the absolute path to an asset.
70 | *
71 | * @param string $path
72 | * @return string
73 | */
74 | public function buildAbsolutePath($path)
75 | {
76 | if (is_null($path)) return $path;
77 |
78 | return realpath($path) ?: $path;
79 | }
80 |
81 | /**
82 | * Build the relative path to an asset.
83 | *
84 | * @param string $path
85 | * @return string
86 | */
87 | public function buildRelativePath($path)
88 | {
89 | if (is_null($path)) return $path;
90 |
91 | $relativePath = str_replace(array(realpath($this->publicPath), '\\'), array('', '/'), $path);
92 |
93 | // If the asset is not a remote asset then we'll trim the relative path even further to remove
94 | // any unnecessary leading or trailing slashes. This will leave us with a nice relative path.
95 | if ( ! starts_with($path, '//') and ! (bool) filter_var($path, FILTER_VALIDATE_URL))
96 | {
97 | $relativePath = trim($relativePath, '/');
98 |
99 | // If the given path is the same as the built relative path then the asset appears to be
100 | // outside of the public directory. If this is the case then we'll use an MD5 hash of
101 | // the assets path as the relative path to the asset.
102 | if (trim(str_replace('\\', '/', $path), '/') == trim($relativePath, '/'))
103 | {
104 | $path = pathinfo($path);
105 |
106 | $relativePath = md5($path['dirname']).'/'.$path['basename'];
107 | }
108 | }
109 |
110 | return $relativePath;
111 | }
112 |
113 | /**
114 | * Get the next asset order.
115 | *
116 | * @return int
117 | */
118 | protected function nextAssetOrder()
119 | {
120 | return ++$this->assetsProduced;
121 | }
122 |
123 | }
--------------------------------------------------------------------------------
/tests/Basset/Builder/FilesystemCleanerTest.php:
--------------------------------------------------------------------------------
1 | environment = m::mock('Basset\Environment');
17 | $this->manifest = m::mock('Basset\Manifest\Manifest');
18 | $this->files = m::mock('Illuminate\Filesystem\Filesystem');
19 |
20 | $this->cleaner = new Basset\Builder\FilesystemCleaner($this->environment, $this->manifest, $this->files, 'path/to/builds');
21 |
22 | $this->manifest->shouldReceive('save')->atLeast()->once();
23 | }
24 |
25 |
26 | public function testForgettingCollectionFromManifestThatNoLongerExistsOnEnvironment()
27 | {
28 | $this->environment->shouldReceive('offsetExists')->once()->with('foo')->andReturn(false);
29 | $this->manifest->shouldReceive('get')->once()->with('foo')->andReturn($entry = m::mock('Basset\Manifest\Entry'));
30 | $this->manifest->shouldReceive('forget')->once()->with('foo');
31 |
32 | $entry->shouldReceive('hasProductionFingerprints')->once()->andReturn(false);
33 | $this->files->shouldReceive('glob')->with('path/to/builds/foo-*.*')->andReturn(array('path/to/builds/foo-123.css'));
34 | $this->files->shouldReceive('delete')->with('path/to/builds/foo-123.css');
35 | $entry->shouldReceive('resetProductionFingerprints')->once();
36 |
37 | $entry->shouldReceive('hasDevelopmentAssets')->once()->andReturn(false);
38 | $this->files->shouldReceive('deleteDirectory')->once()->with('path/to/builds/foo');
39 | $entry->shouldReceive('resetDevelopmentAssets')->once();
40 |
41 | $this->cleaner->clean('foo');
42 | }
43 |
44 |
45 | public function testCleaningOfManifestFilesOnFilesystem()
46 | {
47 | $this->manifest->shouldReceive('get')->once()->with('foo')->andReturn($entry = m::mock('Basset\Manifest\Entry'));
48 | $this->environment->shouldReceive('offsetExists')->times(3)->with('foo')->andReturn(true);
49 | $this->environment->shouldReceive('offsetGet')->once()->with('foo')->andReturn($collection = m::mock('Basset\Collection'));
50 |
51 | $collection->shouldReceive('getIdentifier')->twice()->andReturn('foo');
52 |
53 | $entry->shouldReceive('getProductionFingerprints')->once()->andReturn(array(
54 | 'foo-37b51d194a7513e45b56f6524f2d51f2.css',
55 | 'bar-acbd18db4cc2f85cedef654fccc4a4d8.js'
56 | ));
57 |
58 | $this->files->shouldReceive('glob')->once()->with('path/to/builds/foo-*.css')->andReturn(array(
59 | 'path/to/builds/foo-37b51d194a7513e45b56f6524f2d51f2.css',
60 | 'path/to/builds/foo-asfjkb8912h498hacn8casc8h8942102.css'
61 | ));
62 | $this->files->shouldReceive('delete')->once()->with('path/to/builds/foo-asfjkb8912h498hacn8casc8h8942102.css')->andReturn(true);
63 | $this->files->shouldReceive('glob')->once()->with('path/to/builds/bar-*.js')->andReturn(array());
64 |
65 | $entry->shouldReceive('getDevelopmentAssets')->once()->andReturn(array(
66 | 'stylesheets' => array('bar/baz-37b51d194a7513e45b56f6524f2d51f2.css', 'bar/qux-acbd18db4cc2f85cedef654fccc4a4d8.css'),
67 | 'javascripts' => array()
68 | ));
69 |
70 | $this->files->shouldReceive('glob')->once()->with('path/to/builds/foo/bar/baz-*.css')->andReturn(array('path/to/builds/foo/bar/baz-37b51d194a7513e45b56f6524f2d51f2.css'));
71 | $this->files->shouldReceive('glob')->once()->with('path/to/builds/foo/bar/qux-*.css')->andReturn(array());
72 |
73 | $entry->shouldReceive('hasProductionFingerprints')->once()->andReturn(true);
74 | $entry->shouldReceive('hasDevelopmentAssets')->once()->andReturn(true);
75 |
76 | $this->cleaner->clean('foo');
77 | }
78 |
79 |
80 | }
--------------------------------------------------------------------------------
/tests/Basset/CollectionTest.php:
--------------------------------------------------------------------------------
1 | collection = new Collection($this->directory = m::mock('Basset\Directory'), 'foo');
20 | }
21 |
22 |
23 | public function testGetIdentifierOfCollection()
24 | {
25 | $this->assertEquals('foo', $this->collection->getIdentifier());
26 | }
27 |
28 |
29 | public function testGetDefaultDirectory()
30 | {
31 | $this->assertEquals($this->directory, $this->collection->getDefaultDirectory());
32 | }
33 |
34 |
35 | public function testGetExtensionFromGroup()
36 | {
37 | $this->assertEquals('css', $this->collection->getExtension('stylesheets'));
38 | $this->assertEquals('js', $this->collection->getExtension('javascripts'));
39 | }
40 |
41 |
42 | public function testGettingCollectionAssetsWithDefaultOrdering()
43 | {
44 | $this->directory->shouldReceive('getAssets')->andReturn($expected = new IlluminateCollection(array(
45 | $this->getAssetInstance('bar.css', 'path/to/bar.css', 'stylesheets', 1),
46 | $this->getAssetInstance('baz.css', 'path/to/baz.css', 'stylesheets', 2)
47 | )));
48 |
49 | $this->assertEquals($expected->all(), $this->collection->getAssets('stylesheets')->all());
50 | }
51 |
52 |
53 | public function testGettingCollectionWithMultipleAssetGroupsReturnsOnlyRequestedGroup()
54 | {
55 | $this->directory->shouldReceive('getAssets')->andReturn(new IlluminateCollection(array(
56 | $assets[] = $this->getAssetInstance('foo.css', 'path/to/foo.css', 'stylesheets', 1),
57 | $assets[] = $this->getAssetInstance('bar.js', 'path/to/bar.js', 'javascripts', 2),
58 | $assets[] = $this->getAssetInstance('baz.js', 'path/to/baz.js', 'javascripts', 3),
59 | $assets[] = $this->getAssetInstance('qux.css', 'path/to/qux.css', 'stylesheets', 4)
60 | )));
61 |
62 | $expected = array(0 => $assets[0], 3 => $assets[3]);
63 | $this->assertEquals($expected, $this->collection->getAssets('stylesheets')->all());
64 | }
65 |
66 |
67 | public function testGettingCollectionAssetsWithCustomOrdering()
68 | {
69 | $this->directory->shouldReceive('getAssets')->andReturn(new IlluminateCollection(array(
70 | $assets[] = $this->getAssetInstance('foo.css', 'path/to/foo.css', 'stylesheets', 1), // Becomes 2nd
71 | $assets[] = $this->getAssetInstance('bar.css', 'path/to/bar.css', 'stylesheets', 2), // Becomes 4th
72 | $assets[] = $this->getAssetInstance('baz.css', 'path/to/baz.css', 'stylesheets', 1), // Becomes 1st
73 | $assets[] = $this->getAssetInstance('qux.css', 'path/to/qux.css', 'stylesheets', 4), // Becomes 5th
74 | $assets[] = $this->getAssetInstance('zin.css', 'path/to/zin.css', 'stylesheets', 3) // Becomes 3rd
75 | )));
76 |
77 | $expected = array($assets[2], $assets[0], $assets[4], $assets[1], $assets[3]);
78 | $this->assertEquals($expected, $this->collection->getAssets('stylesheets')->all());
79 | }
80 |
81 |
82 | public function testGettingCollectionRawAssets()
83 | {
84 | $this->directory->shouldReceive('getAssets')->andReturn(new IlluminateCollection(array(
85 | $assets[] = $this->getAssetInstance('foo.css', 'path/to/foo.css', 'stylesheets', 1),
86 | $assets[] = $this->getAssetInstance('bar.css', 'path/to/bar.css', 'stylesheets', 2)
87 | )));
88 |
89 | $assets[1]->raw();
90 |
91 | $this->assertEquals(array(1 => $assets[1]), $this->collection->getAssetsOnlyRaw('stylesheets')->all());
92 | }
93 |
94 |
95 | public function getAssetInstance($relative, $absolute, $group, $order)
96 | {
97 | $asset = new Asset(m::mock('Illuminate\Filesystem\Filesystem'), m::mock('Basset\Factory\FactoryManager'), 'testing', $absolute, $relative);
98 |
99 | return $asset->setOrder($order)->setGroup($group);
100 | }
101 |
102 |
103 | }
--------------------------------------------------------------------------------
/src/Basset/Console/BassetCommand.php:
--------------------------------------------------------------------------------
1 | manifest = $manifest;
67 | $this->environment = $environment;
68 | $this->cleaner = $cleaner;
69 | }
70 |
71 | /**
72 | * Execute the console command.
73 | *
74 | * @return void
75 | */
76 | public function fire()
77 | {
78 | if ( ! $this->input->getOption('delete-manifest') and ! $this->input->getOption('tidy-up'))
79 | {
80 | $this->line('Basset version '.Basset::VERSION.'');
81 | }
82 | else
83 | {
84 | if ($this->input->getOption('delete-manifest'))
85 | {
86 | $this->deleteCollectionManifest();
87 | }
88 |
89 | if ($this->input->getOption('tidy-up'))
90 | {
91 | $this->tidyUpFilesystem();
92 | }
93 | }
94 | }
95 |
96 | /**
97 | * Delete the collection manifest.
98 | *
99 | * @return void
100 | */
101 | protected function deleteCollectionManifest()
102 | {
103 | if ($this->manifest->delete())
104 | {
105 | $this->info('Manifest has been deleted. All collections will are now required to be rebuilt.');
106 | }
107 | else
108 | {
109 | $this->comment('Manifest does not exist or could not be deleted.');
110 | }
111 | }
112 |
113 | /**
114 | * Tidy up the filesystem with the build cleaner.
115 | *
116 | * @return void
117 | */
118 | protected function tidyUpFilesystem()
119 | {
120 | $collections = array_keys($this->environment->all()) + array_keys($this->manifest->all());
121 |
122 | foreach ($collections as $collection)
123 | {
124 | if ($this->input->getOption('verbose'))
125 | {
126 | $this->line('['.$collection.'] Cleaning up files and manifest entries.');
127 | }
128 |
129 | $this->cleaner->clean($collection);
130 | }
131 |
132 | $this->input->getOption('verbose') and $this->line('');
133 |
134 | $this->info('The filesystem and manifest have been tidied up.');
135 | }
136 |
137 | /**
138 | * Get the console command options.
139 | *
140 | * @return array
141 | */
142 | protected function getOptions()
143 | {
144 | return array(
145 | array('delete-manifest', null, InputOption::VALUE_NONE, 'Delete the collection manifest'),
146 | array('tidy-up', null, InputOption::VALUE_NONE, 'Tidy up the outdated collections and manifest entries')
147 | );
148 | }
149 |
150 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## No Longer Maintained
2 |
3 | Basset is no longer being maintained by me (Jason Lewis). Appologies to those of you that have invested time into this package. Feel free to fork it if
4 | you feel the need but I strongly urged you to shift over to using [Grunt](http://gruntjs.com/) to manage the compiling and concatenation of your assets.
5 |
6 | Once again, I'm sorry, I just don't have the time and Grunt does an amazing job.
7 |
8 | ## Basset for Laravel 4
9 |
10 | [](http://travis-ci.org/jasonlewis/basset)
11 |
12 | Basset is a better asset management package for the Laravel framework. Basset shares the same philosophy as Laravel. Development should be an enjoyable and fulfilling experience. When it comes to managing your assets it can become quite complex and a pain in the backside. These days developers are able to use a range of pre-processors such as Sass, Less, and CoffeeScript. Basset is able to handle the processing of these assets instead of relying on a number of individual tools.
13 |
14 | ### Installation
15 |
16 | - [Basset on Packagist](https://packagist.org/packages/jasonlewis/basset)
17 | - [Basset on GitHub](https://github.com/jasonlewis/basset)
18 |
19 | To get the latest version of Basset simply require it in your `composer.json` file.
20 |
21 | ~~~
22 | "jasonlewis/basset": "dev-master"
23 | ~~~
24 |
25 | You'll then need to run `composer install` to download it and have the autoloader updated.
26 |
27 | > Note that once Basset has a stable version tagged you should use a tagged release instead of the master branch.
28 |
29 | Once Basset is installed you need to register the service provider with the application. Open up `app/config/app.php` and find the `providers` key.
30 |
31 | ~~~
32 | 'providers' => array(
33 |
34 | 'Basset\BassetServiceProvider'
35 |
36 | )
37 | ~~~
38 |
39 | Basset also ships with a facade which provides the static syntax for creating collections. You can register the facade in the `aliases` key of your `app/config/app.php` file.
40 |
41 | ~~~
42 | 'aliases' => array(
43 |
44 | 'Basset' => 'Basset\Facade'
45 |
46 | )
47 | ~~~
48 |
49 | ### Documentation
50 |
51 | [View the official documentation](http://jasonlewis.me/code/basset/4.0).
52 |
53 | ### Changes
54 |
55 | #### v4.0.0 Beta 3
56 |
57 | - Split the collections and aliases into their own configuration files.
58 | - Filter method chaining with syntactical sugar by prefixing with `and`, e.g., `andWhenProductionBuild()`.
59 |
60 | #### v4.0.0 Beta 2
61 |
62 | - Added logging when assets, directories, and filters are not found or fail to load.
63 | - Allow logging to be enabled or disabled via configuration.
64 | - Warn users when cURL is being used to detect an assets group.
65 | - Allow an array of filters to be applied to an asset.
66 | - Added `whenProductionBuild` and `whenDevelopmentBuild` as filter requirements.
67 | - `CssMin` and `JsMin` are only applied on a production build and not on the production environment.
68 | - Added `raw` method as an alias to `exclude`.
69 | - Entire directory or collection can be set as raw so original path is used instead of assets being built.
70 | - Development builds only happen for a collection that is used on the loaded request.
71 | - Added `rawOnEnvironment` to serve the asset raw on a given environment or environments.
72 |
73 |
74 | #### v4.0.0 Beta 1
75 |
76 | - Collections are displayed with `basset_javascripts()` and `basset_stylesheets()`.
77 | - Simplified the asset finding process.
78 | - Can no longer prefix paths with `path:` for an absolute path, use a relative path from public directory instead.
79 | - Requirements can be applied to filters to prevent application if certain conditions are not met.
80 | - Filters can find any missing constructor arguments such as the path to Node, Ruby, etc.
81 | - Default `application` collection is bundled.
82 | - `basset:compile` command is now `basset:build`.
83 | - Old collection builds are cleaned automatically but can be cleaned manually with `basset --tidy-up`.
84 | - Packages can be registered with `Basset::package()` and assets can be added using the familiar namespace syntax found throughout Laravel.
85 | - `Csso` support with `CssoFilter`.
86 | - Fixed issues with `UriRewriteFilter`.
87 | - Development collections are pre-built before every page load.
88 | - Build and serve pre-compressed collections.
89 | - Use custom format when displaying collections.
90 | - Added in Blade view helpers: `@javascripts`, `@stylesheets`, and `@assets`.
91 | - Assets maintain the order that they were added.
92 |
--------------------------------------------------------------------------------
/src/Basset/Manifest/Manifest.php:
--------------------------------------------------------------------------------
1 | files = $files;
46 | $this->manifestPath = $manifestPath;
47 | $this->entries = new \Illuminate\Support\Collection;
48 | }
49 |
50 | /**
51 | * Determine if the manifest has a given collection entry.
52 | *
53 | * @param string $collection
54 | * @return bool
55 | */
56 | public function has($collection)
57 | {
58 | return ! is_null($this->get($collection));
59 | }
60 |
61 | /**
62 | * Get a collection entry from the manifest or create a new entry.
63 | *
64 | * @param string|\Basset\Collection $collection
65 | * @return null|\Basset\Manifest\Entry
66 | */
67 | public function get($collection)
68 | {
69 | $collection = $this->getCollectionNameFromInstance($collection);
70 |
71 | return isset($this->entries[$collection]) ? $this->entries[$collection] : null;
72 | }
73 |
74 | /**
75 | * Make a collection entry if it does not already exist on the manifest.
76 | *
77 | * @param string|\Basset\Collection $collection
78 | * @return \Basset\Manifest\Entry
79 | */
80 | public function make($collection)
81 | {
82 | $collection = $this->getCollectionNameFromInstance($collection);
83 |
84 | $this->dirty = true;
85 |
86 | return $this->get($collection) ?: $this->entries[$collection] = new Entry;
87 | }
88 |
89 | /**
90 | * Forget a collection from the repository.
91 | *
92 | * @param string|\Basset\Collection $collection
93 | * @return void
94 | */
95 | public function forget($collection)
96 | {
97 | $collection = $this->getCollectionNameFromInstance($collection);
98 |
99 | if ($this->has($collection))
100 | {
101 | $this->dirty = true;
102 |
103 | unset($this->entries[$collection]);
104 | }
105 | }
106 |
107 | /**
108 | * Get all the entries.
109 | *
110 | * @return array
111 | */
112 | public function all()
113 | {
114 | return $this->entries;
115 | }
116 |
117 | /**
118 | * Get the collections identifier from a collection instance.
119 | *
120 | * @param string|\Basset\Collection $collection
121 | * @return string
122 | */
123 | protected function getCollectionNameFromInstance($collection)
124 | {
125 | return $collection instanceof Collection ? $collection->getIdentifier() : $collection;
126 | }
127 |
128 | /**
129 | * Loads and registers the manifest entries.
130 | *
131 | * @return void
132 | */
133 | public function load()
134 | {
135 | $path = $this->manifestPath.'/collections.json';
136 |
137 | if ($this->files->exists($path) and is_array($manifest = json_decode($this->files->get($path), true)))
138 | {
139 | foreach ($manifest as $key => $entry)
140 | {
141 | $entry = new Entry($entry['fingerprints'], $entry['development']);
142 |
143 | $this->entries->put($key, $entry);
144 | }
145 | }
146 | }
147 |
148 | /**
149 | * Save the manifest.
150 | *
151 | * @return bool
152 | */
153 | public function save()
154 | {
155 | if ($this->dirty)
156 | {
157 | $path = $this->manifestPath.'/collections.json';
158 |
159 | $this->dirty = false;
160 |
161 | return (bool) $this->files->put($path, $this->entries->toJson());
162 | }
163 |
164 | return false;
165 | }
166 |
167 | /**
168 | * Delete the manifest.
169 | *
170 | * @return bool
171 | */
172 | public function delete()
173 | {
174 | if ($this->files->exists($path = $this->manifestPath.'/collections.json'))
175 | {
176 | return $this->files->delete($path);
177 | }
178 | else
179 | {
180 | return false;
181 | }
182 | }
183 |
184 | }
--------------------------------------------------------------------------------
/src/Basset/Collection.php:
--------------------------------------------------------------------------------
1 | directory = $directory;
29 | $this->identifier = $identifier;
30 | }
31 |
32 | /**
33 | * Get all the assets filtered by a group and without the raw assets.
34 | *
35 | * @param string $group
36 | * @return \Illuminate\Support\Collection
37 | */
38 | public function getAssetsWithoutRaw($group = null)
39 | {
40 | return $this->getAssets($group, false);
41 | }
42 |
43 | /**
44 | * Get all the assets filtered by a group and with the raw assets.
45 | *
46 | * @param string $group
47 | * @return \Illuminate\Support\Collection
48 | */
49 | public function getAssetsWithRaw($group = null)
50 | {
51 | return $this->getAssets($group, true);
52 | }
53 |
54 | /**
55 | * Get all the assets filtered by a group but only if the assets are raw.
56 | *
57 | * @param string $group
58 | * @return \Illuminate\Support\Collection
59 | */
60 | public function getAssetsOnlyRaw($group = null)
61 | {
62 | // Get all the assets for the given group and filter out assets that aren't listed
63 | // as being raw.
64 | $assets = $this->getAssets($group, true)->filter(function($asset)
65 | {
66 | return $asset->isRaw();
67 | });
68 |
69 | return $assets;
70 | }
71 |
72 | /**
73 | * Get all the assets filtered by a group and if to include the raw assets.
74 | *
75 | * @param string $group
76 | * @param bool $raw
77 | * @return \Illuminate\Support\Collection
78 | */
79 | public function getAssets($group = null, $raw = true)
80 | {
81 | // Spin through all of the assets that belong to the given group and push them on
82 | // to the end of the array.
83 | $assets = clone $this->directory->getAssets();
84 |
85 | foreach ($assets as $key => $asset)
86 | {
87 | if ( ! $raw and $asset->isRaw() or ! is_null($group) and ! $asset->{'is'.ucfirst(str_singular($group))}())
88 | {
89 | $assets->forget($key);
90 | }
91 | }
92 |
93 | // Spin through each of the assets and build an ordered array of assets. Once
94 | // we have the ordered array we'll transform it into a collection and apply
95 | // the collection wide filters to each asset.
96 | $ordered = array();
97 |
98 | foreach ($assets as $asset)
99 | {
100 | $this->orderAsset($asset, $ordered);
101 | }
102 |
103 | return new \Illuminate\Support\Collection($ordered);
104 | }
105 |
106 | /**
107 | * Orders the array of assets as they were defined or on a user ordered basis.
108 | *
109 | * @param \Basset\Asset $asset
110 | * @param array $assets
111 | * @return void
112 | */
113 | protected function orderAsset(Asset $asset, array &$assets)
114 | {
115 | $order = $asset->getOrder() and $order--;
116 |
117 | // If an asset already exists at the given order key then we'll add one to the order
118 | // so the asset essentially appears after the existing asset. This makes sense since
119 | // the array of assets has been reversed, so if the last asset was told to be first
120 | // then when we finally get to the first added asset it's added second.
121 | if (array_key_exists($order, $assets))
122 | {
123 | array_splice($assets, $order, 0, array(null));
124 | }
125 |
126 | $assets[$order] = $asset;
127 |
128 | ksort($assets);
129 | }
130 |
131 | /**
132 | * Get the identifier of the collection.
133 | *
134 | * @return string
135 | */
136 | public function getIdentifier()
137 | {
138 | return $this->identifier;
139 | }
140 |
141 | /**
142 | * Get the default directory.
143 | *
144 | * @return \Basset\Directory
145 | */
146 | public function getDefaultDirectory()
147 | {
148 | return $this->directory;
149 | }
150 |
151 | /**
152 | * Determine an extension based on the group.
153 | *
154 | * @param string $group
155 | * @return string
156 | */
157 | public function getExtension($group)
158 | {
159 | return str_plural($group) == 'stylesheets' ? 'css' : 'js';
160 | }
161 |
162 | /**
163 | * Dynamically call methods on the default directory.
164 | *
165 | * @param string $method
166 | * @param array $parameters
167 | * @return mixed
168 | */
169 | public function __call($method, $parameters)
170 | {
171 | return call_user_func_array(array($this->directory, $method), $parameters);
172 | }
173 |
174 | }
--------------------------------------------------------------------------------
/src/Basset/Environment.php:
--------------------------------------------------------------------------------
1 | factory = $factory;
41 | $this->finder = $finder;
42 | }
43 |
44 | /**
45 | * Alias of \Basset\Environment::collection()
46 | *
47 | * @param string $name
48 | * @param \Closure $callback
49 | * @return \Basset\Collection
50 | */
51 | public function make($name, Closure $callback = null)
52 | {
53 | return $this->collection($name, $callback);
54 | }
55 |
56 | /**
57 | * Create or return an existing collection.
58 | *
59 | * @param string $identifier
60 | * @param \Closure $callback
61 | * @return \Basset\Collection
62 | */
63 | public function collection($identifier, Closure $callback = null)
64 | {
65 | if ( ! isset($this->collections[$identifier]))
66 | {
67 | $directory = $this->prepareDefaultDirectory();
68 |
69 | $this->collections[$identifier] = new Collection($directory, $identifier);
70 | }
71 |
72 | // If the collection has been given a callable closure then we'll execute the closure with
73 | // the collection instance being the only parameter given. This allows users to begin
74 | // using the collection instance to add assets.
75 | if (is_callable($callback))
76 | {
77 | call_user_func($callback, $this->collections[$identifier]);
78 | }
79 |
80 | return $this->collections[$identifier];
81 | }
82 |
83 | /**
84 | * Prepare the default directory for a new collection.
85 | *
86 | * @return \Basset\Directory
87 | */
88 | protected function prepareDefaultDirectory()
89 | {
90 | $path = $this->finder->setWorkingDirectory('/');
91 |
92 | return new Directory($this->factory, $this->finder, $path);
93 | }
94 |
95 | /**
96 | * Get all collections.
97 | *
98 | * @return array
99 | */
100 | public function all()
101 | {
102 | return $this->collections;
103 | }
104 |
105 | /**
106 | * Determine if a collection exists.
107 | *
108 | * @param string $name
109 | * @return bool
110 | */
111 | public function has($name)
112 | {
113 | return isset($this->collections[$name]);
114 | }
115 |
116 | /**
117 | * Register a package with the environment.
118 | *
119 | * @param string $package
120 | * @param string $namespace
121 | * @return void
122 | */
123 | public function package($package, $namespace = null)
124 | {
125 | if (is_null($namespace))
126 | {
127 | list($vendor, $namespace) = explode('/', $package);
128 | }
129 |
130 | $this->finder->addNamespace($namespace, $package);
131 | }
132 |
133 | /**
134 | * Register an array of collections.
135 | *
136 | * @param array $collections
137 | * @return void
138 | */
139 | public function collections(array $collections)
140 | {
141 | foreach ($collections as $name => $callback)
142 | {
143 | $this->make($name, $callback);
144 | }
145 | }
146 |
147 | /**
148 | * Set a collection offset.
149 | *
150 | * @param string $offset
151 | * @param mixed $value
152 | * @return void
153 | */
154 | public function offsetSet($offset, $value)
155 | {
156 | if (is_null($offset))
157 | {
158 | throw new InvalidArgumentException('Collection identifier not given.');
159 | }
160 |
161 | $this->collection($offset, $value);
162 | }
163 |
164 | /**
165 | * Get a collection offset.
166 | *
167 | * @param string $offset
168 | * @return null|\Basset\Collection
169 | */
170 | public function offsetGet($offset)
171 | {
172 | return $this->has($offset) ? $this->collection($offset) : null;
173 | }
174 |
175 | /**
176 | * Unset a collection offset.
177 | *
178 | * @param string $offset
179 | * @return void
180 | */
181 | public function offsetUnset($offset)
182 | {
183 | unset($this->collections[$offset]);
184 | }
185 |
186 | /**
187 | * Determine if a collection offset exists.
188 | *
189 | * @param string $offset
190 | * @return bool
191 | */
192 | public function offsetExists($offset)
193 | {
194 | return $this->has($offset);
195 | }
196 |
197 | }
--------------------------------------------------------------------------------
/tests/Basset/Manifest/EntryTest.php:
--------------------------------------------------------------------------------
1 | data = array(
19 | 'fingerprints' => array(
20 | 'stylesheets' => 'foo-123.css'
21 | ),
22 | 'development' => array(
23 | 'stylesheets' => array(
24 | 'bar/baz.sass' => 'bar/baz.css'
25 | ),
26 | 'javascripts' => array(
27 | 'baz/qux.coffee' => 'baz/qux.js'
28 | )
29 | )
30 | );
31 |
32 | $this->entry = new Entry($this->data['fingerprints'], $this->data['development']);
33 | }
34 |
35 |
36 | public function testDefaultArrayIsParsedCorrectly()
37 | {
38 | $this->assertEquals($this->data, $this->entry->toArray());
39 | }
40 |
41 |
42 | public function testAddingDevelopmentAssetToEntry()
43 | {
44 | $this->entry->addDevelopmentAsset('foo/bar.sass', 'foo/bar.css', 'stylesheets');
45 | $this->data['development']['stylesheets']['foo/bar.sass'] = 'foo/bar.css';
46 | $this->assertEquals($this->data, $this->entry->toArray());
47 | }
48 |
49 |
50 | public function testAddingDevelopmentAssetToEntryFromAssetInstance()
51 | {
52 | $asset = new Asset($files = m::mock('Illuminate\Filesystem\Filesystem'), m::mock('Basset\Factory\FactoryManager'), 'testing', 'foo/bar.sass', 'foo/bar.sass');
53 | $files->shouldReceive('lastModified')->once()->with('foo/bar.sass')->andReturn(time());
54 | $this->entry->addDevelopmentAsset($asset);
55 | $this->data['development']['stylesheets']['foo/bar.sass'] = 'foo/bar-'.md5('[]'.time()).'.css';
56 | $this->assertEquals($this->data, $this->entry->toArray());
57 | }
58 |
59 |
60 | public function testGettingDevelopmentAsset()
61 | {
62 | $this->assertEquals('bar/baz.css', $this->entry->getDevelopmentAsset('bar/baz.sass', 'stylesheets'));
63 | }
64 |
65 |
66 | public function testGettingInvalidDevelopmentAssetReturnsNull()
67 | {
68 | $this->assertNull($this->entry->getDevelopmentAsset('foo/bar.sass', 'stylesheets'));
69 | }
70 |
71 |
72 | public function testGettingDevelopmentAssetFromAssetInstance()
73 | {
74 | $asset = m::mock('Basset\Asset');
75 | $asset->shouldReceive('getGroup')->once()->andReturn('stylesheets');
76 | $asset->shouldReceive('getRelativePath')->once()->andReturn('bar/baz.sass');
77 | $this->assertEquals('bar/baz.css', $this->entry->getDevelopmentAsset($asset));
78 | }
79 |
80 |
81 | public function testCheckingForDevelopmentAssetExistence()
82 | {
83 | $this->assertFalse($this->entry->hasDevelopmentAsset('foo/bar.css', 'stylesheets'));
84 | $this->assertTrue($this->entry->hasDevelopmentAsset('bar/baz.sass', 'stylesheets'));
85 | }
86 |
87 |
88 | public function testGetAllDevelopmentAssets()
89 | {
90 | $this->assertEquals($this->data['development'], $this->entry->getDevelopmentAssets());
91 | }
92 |
93 |
94 | public function testGetAllDevelopmentAssetsForGivenGroup()
95 | {
96 | $this->assertEquals($this->data['development']['javascripts'], $this->entry->getDevelopmentAssets('javascripts'));
97 | }
98 |
99 |
100 | public function testCheckingForExistenceOfAnyDevelopmentAssets()
101 | {
102 | $this->assertTrue($this->entry->hasDevelopmentAssets());
103 | }
104 |
105 |
106 | public function testCheckingForExistenceOfSpecificDevelopmentAssetsGroup()
107 | {
108 | $this->entry->resetDevelopmentAssets('javascripts');
109 | $this->assertFalse($this->entry->hasDevelopmentAssets('javascripts'));
110 | }
111 |
112 |
113 | public function testResettingAllDevelopmentAssets()
114 | {
115 | $this->entry->resetDevelopmentAssets();
116 | $this->assertEmpty($this->entry->getDevelopmentAssets());
117 | }
118 |
119 |
120 | public function testResettingSpecificDevelopmentAssetsGroup()
121 | {
122 | $this->entry->resetDevelopmentAssets('javascripts');
123 | $this->assertEmpty($this->entry->getDevelopmentAssets('javascripts'));
124 | }
125 |
126 |
127 | public function testSettingProductionFingerprintOnGroup()
128 | {
129 | $this->entry->setProductionFingerprint('javascripts', 'foo-321.js');
130 | $this->assertEquals('foo-321.js', $this->entry->getProductionFingerprint('javascripts'));
131 | }
132 |
133 |
134 | public function testCheckingForExistenceOfFingerprint()
135 | {
136 | $this->assertTrue($this->entry->hasProductionFingerprint('stylesheets'));
137 | $this->assertFalse($this->entry->hasProductionFingerprint('javascripts'));
138 | }
139 |
140 |
141 | public function testGettingAllProductionFingerprints()
142 | {
143 | $this->assertEquals($this->data['fingerprints'], $this->entry->getProductionFingerprints());
144 | }
145 |
146 |
147 | public function testGettingEntryAsJson()
148 | {
149 | $this->assertEquals(json_encode($this->data), $this->entry->toJson());
150 | }
151 |
152 |
153 | public function testGettingEntryAsArray()
154 | {
155 | $this->assertEquals($this->data, $this->entry->toArray());
156 | }
157 |
158 |
159 | }
--------------------------------------------------------------------------------
/src/Basset/Manifest/Entry.php:
--------------------------------------------------------------------------------
1 | fingerprints = $fingerprints;
33 | $this->development = $development;
34 | }
35 |
36 | /**
37 | * Add a development asset.
38 | *
39 | * @param string|\Basset\Asset $value
40 | * @param string $fingerprint
41 | * @param string $group
42 | * @return void
43 | */
44 | public function addDevelopmentAsset($value, $fingerprint = null, $group = null)
45 | {
46 | if ($value instanceof Asset)
47 | {
48 | $group = $value->getGroup();
49 |
50 | $fingerprint = $value->getBuildPath();
51 |
52 | $value = $value->getRelativePath();
53 | }
54 |
55 | $this->development[$group][$value] = $fingerprint;
56 | }
57 |
58 | /**
59 | * Get a development assets build path.
60 | *
61 | * @param string|\Basset\Asset $value
62 | * @param string $group
63 | * @return null|string
64 | */
65 | public function getDevelopmentAsset($value, $group = null)
66 | {
67 | if ($value instanceof Asset)
68 | {
69 | $group = $value->getGroup();
70 |
71 | $value = $value->getRelativePath();
72 | }
73 |
74 | return isset($this->development[$group][$value]) ? $this->development[$group][$value] : null;
75 | }
76 |
77 | /**
78 | * Determine if the entry has a development asset.
79 | *
80 | * @param string|\Basset\Asset $value
81 | * @param string $group
82 | * @return bool
83 | */
84 | public function hasDevelopmentAsset($value, $group = null)
85 | {
86 | return ! is_null($this->getDevelopmentAsset($value, $group));
87 | }
88 |
89 | /**
90 | * Get all or a subset of development assets.
91 | *
92 | * @param string $group
93 | * @return array
94 | */
95 | public function getDevelopmentAssets($group = null)
96 | {
97 | return is_null($group) ? $this->development : $this->development[$group];
98 | }
99 |
100 | /**
101 | * Determine if the entry has any development assets.
102 | *
103 | * @param string $group
104 | * @return bool
105 | */
106 | public function hasDevelopmentAssets($group = null)
107 | {
108 | return is_null($group) ? ! empty($this->development) : ! empty($this->development[$group]);
109 | }
110 |
111 | /**
112 | * Reset the development assets.
113 | *
114 | * @param string $group
115 | * @return void
116 | */
117 | public function resetDevelopmentAssets($group = null)
118 | {
119 | if (is_null($group))
120 | {
121 | $this->development = array();
122 | }
123 | else
124 | {
125 | $this->development[$group] = array();
126 | }
127 | }
128 |
129 | /**
130 | * Set the entry fingerprint.
131 | *
132 | * @param string $group
133 | * @param string $fingerprint
134 | * @return void
135 | */
136 | public function setProductionFingerprint($group, $fingerprint)
137 | {
138 | $this->fingerprints[$group] = $fingerprint;
139 | }
140 |
141 | /**
142 | * Determine if entry has a fingerprint.
143 | *
144 | * @param string $group
145 | * @return bool
146 | */
147 | public function hasProductionFingerprint($group)
148 | {
149 | return ! is_null($this->getProductionFingerprint($group));
150 | }
151 |
152 | /**
153 | * Determine if entry has any fingerprints.
154 | *
155 | * @return bool
156 | */
157 | public function hasProductionFingerprints()
158 | {
159 | return $this->hasProductionFingerprint('stylesheets') or $this->hasProductionFingerprint('javascripts');
160 | }
161 |
162 | /**
163 | * Get the entry fingerprint.
164 | *
165 | * @param string $group
166 | * @return string|null
167 | */
168 | public function getProductionFingerprint($group)
169 | {
170 | return isset($this->fingerprints[$group]) ? $this->fingerprints[$group] : null;
171 | }
172 |
173 | /**
174 | * Get all entry fingerprints.
175 | *
176 | * @return array
177 | */
178 | public function getProductionFingerprints()
179 | {
180 | return $this->fingerprints;
181 | }
182 |
183 | /**
184 | * Reset a production fingerprint.
185 | *
186 | * @param string $group
187 | * @return void
188 | */
189 | public function resetProductionFingerprint($group)
190 | {
191 | $this->fingerprints[$group] = null;
192 | }
193 |
194 | /**
195 | * Reset all production fingerprints.
196 | *
197 | * @return void
198 | */
199 | public function resetProductionFingerprints()
200 | {
201 | $this->fingerprints = array();
202 | }
203 |
204 | /**
205 | * Convert the entry to its JSON representation.
206 | *
207 | * @param int $options
208 | * @return string
209 | */
210 | public function toJson($options = 0)
211 | {
212 | return json_encode($this->toArray(), $options);
213 | }
214 |
215 | /**
216 | * Convert the entry to its array representation.
217 | *
218 | * @return array
219 | */
220 | public function toArray()
221 | {
222 | return get_object_vars($this);
223 | }
224 |
225 | }
--------------------------------------------------------------------------------
/src/Basset/Console/BuildCommand.php:
--------------------------------------------------------------------------------
1 | environment = $environment;
63 | $this->builder = $builder;
64 | $this->cleaner = $cleaner;
65 | }
66 |
67 | /**
68 | * Execute the console command.
69 | *
70 | * @return void
71 | */
72 | public function fire()
73 | {
74 | $this->input->getOption('force') and $this->builder->setForce(true);
75 |
76 | $this->input->getOption('gzip') and $this->builder->setGzip(true);
77 |
78 | if ($production = $this->input->getOption('production'))
79 | {
80 | $this->comment('Starting production build...');
81 | }
82 | else
83 | {
84 | $this->comment('Starting development build...');
85 | }
86 |
87 | $collections = $this->gatherCollections();
88 |
89 | if ($this->input->getOption('gzip') and ! function_exists('gzencode'))
90 | {
91 | $this->error('[gzip] Build will not use Gzip as the required dependencies are not available.');
92 | $this->line('');
93 | }
94 |
95 | foreach ($collections as $name => $collection)
96 | {
97 | if ($production)
98 | {
99 | $this->buildAsProduction($name, $collection);
100 | }
101 | else
102 | {
103 | $this->buildAsDevelopment($name, $collection);
104 | }
105 |
106 | $this->cleaner->clean($name);
107 | }
108 | }
109 |
110 | /**
111 | * Dynamically handle calls to the build methods.
112 | *
113 | * @param string $method
114 | * @param array $parameters
115 | * @return mixed
116 | */
117 | public function __call($method, $parameters)
118 | {
119 | if (in_array($method, array('buildAsDevelopment', 'buildAsProduction')))
120 | {
121 | list($name, $collection) = $parameters;
122 |
123 | try
124 | {
125 | $this->builder->{$method}($collection, 'stylesheets');
126 |
127 | $this->line('['.$name.'] Stylesheets successfully built.');
128 | }
129 | catch (BuildNotRequiredException $error)
130 | {
131 | $this->line('['.$name.'] Stylesheets build was not required for collection.');
132 | }
133 |
134 | try
135 | {
136 | $this->builder->{$method}($collection, 'javascripts');
137 |
138 | $this->line('['.$name.'] Javascripts successfully built.');
139 | }
140 | catch (BuildNotRequiredException $error)
141 | {
142 | $this->line('['.$name.'] Javascripts build was not required for collection.');
143 | }
144 |
145 | $this->line('');
146 | }
147 | else
148 | {
149 | return parent::__call($method, $parameters);
150 | }
151 | }
152 |
153 | /**
154 | * Gather the collections to be built.
155 | *
156 | * @return array
157 | */
158 | protected function gatherCollections()
159 | {
160 | if ( ! is_null($collection = $this->input->getArgument('collection')))
161 | {
162 | if ( ! $this->environment->has($collection))
163 | {
164 | $this->comment('['.$collection.'] Collection not found.');
165 |
166 | return array();
167 | }
168 |
169 | $this->comment('Gathering assets for collection...');
170 |
171 | $collections = array($collection => $this->environment->collection($collection));
172 | }
173 | else
174 | {
175 | $this->comment('Gathering all collections to build...');
176 |
177 | $collections = $this->environment->all();
178 | }
179 |
180 | $this->line('');
181 |
182 | return $collections;
183 | }
184 |
185 | /**
186 | * Get the console command arguments.
187 | *
188 | * @return array
189 | */
190 | protected function getArguments()
191 | {
192 | return array(
193 | array('collection', InputArgument::OPTIONAL, 'The asset collection to build'),
194 | );
195 | }
196 |
197 | /**
198 | * Get the console command options.
199 | *
200 | * @return array
201 | */
202 | protected function getOptions()
203 | {
204 | return array(
205 | array('production', 'p', InputOption::VALUE_NONE, 'Build assets for a production environment'),
206 | array('gzip', null, InputOption::VALUE_NONE, 'Gzip built assets'),
207 | array('force', 'f', InputOption::VALUE_NONE, 'Forces a re-build of the collection')
208 | );
209 | }
210 |
211 | }
--------------------------------------------------------------------------------
/tests/Basset/AssetFinderTest.php:
--------------------------------------------------------------------------------
1 | files = m::mock('Illuminate\Filesystem\Filesystem');
19 | $this->config = m::mock('Illuminate\Config\Repository');
20 |
21 | $this->finder = new AssetFinder($this->files, $this->config, 'path/to/public');
22 | }
23 |
24 |
25 | public function testFindRemotelyHostedAsset()
26 | {
27 | $this->config->shouldReceive('get')->once()->with('basset::aliases.assets.http://foo.bar/baz.css', 'http://foo.bar/baz.css')->andReturn('http://foo.bar/baz.css');
28 |
29 | $this->assertEquals('http://foo.bar/baz.css', $this->finder->find('http://foo.bar/baz.css'));
30 | }
31 |
32 |
33 | public function testFindRelativeProtocolRemotelyHostedAsset()
34 | {
35 | $this->config->shouldReceive('get')->once()->with('basset::aliases.assets.//foo.bar/baz.css', '//foo.bar/baz.css')->andReturn('//foo.bar/baz.css');
36 |
37 | $this->assertEquals('//foo.bar/baz.css', $this->finder->find('//foo.bar/baz.css'));
38 | }
39 |
40 |
41 | public function testFindPackageAsset()
42 | {
43 | $this->finder->addNamespace('bar', 'foo/bar');
44 |
45 | $this->config->shouldReceive('get')->once()->with('basset::aliases.assets.bar::baz.css', 'bar::baz.css')->andReturn('bar::baz.css');
46 | $this->files->shouldReceive('exists')->once()->with('path/to/public/packages/foo/bar/baz.css')->andReturn(true);
47 |
48 | $this->assertEquals('path/to/public/packages/foo/bar/baz.css', $this->finder->find('bar::baz.css'));
49 | }
50 |
51 |
52 | /**
53 | * @expectedException Basset\Exceptions\AssetNotFoundException
54 | */
55 | public function testFindPackageAssetWithNoSetPackageThrowsNotFoundException()
56 | {
57 | $this->config->shouldReceive('get')->once()->with('basset::aliases.assets.bar::baz.css', 'bar::baz.css')->andReturn('bar::baz.css');
58 |
59 | $this->files->shouldReceive('exists')->once()->with('path/to/public/bar::baz.css')->andReturn(false);
60 | $this->files->shouldReceive('exists')->once()->with('bar::baz.css')->andReturn(false);
61 |
62 | $this->assertNull($this->finder->find('bar::baz.css'));
63 | }
64 |
65 |
66 | public function testFindWorkingDirectoryAsset()
67 | {
68 | $this->config->shouldReceive('get')->once()->with('basset::aliases.assets.foo.css', 'foo.css')->andReturn('foo.css');
69 |
70 | $this->files->shouldReceive('exists')->once()->with('path/to/public/working/directory')->andReturn(true);
71 | $this->finder->setWorkingDirectory('working/directory');
72 | $this->files->shouldReceive('exists')->once()->with('path/to/public/working/directory/foo.css')->andReturn(true);
73 |
74 | $this->assertEquals('path/to/public/working/directory/foo.css', $this->finder->find('foo.css'));
75 | }
76 |
77 |
78 | public function testFindPublicPathAsset()
79 | {
80 | $this->config->shouldReceive('get')->once()->with('basset::aliases.assets.foo.css', 'foo.css')->andReturn('foo.css');
81 |
82 | $this->files->shouldReceive('exists')->once()->with('path/to/public/foo.css')->andReturn(true);
83 |
84 | $this->assertEquals('path/to/public/foo.css', $this->finder->find('foo.css'));
85 | }
86 |
87 |
88 | public function testFindAbsolutePathAsset()
89 | {
90 | $this->config->shouldReceive('get')->once()->with('basset::aliases.assets./absolute/path/to/foo.css', '/absolute/path/to/foo.css')->andReturn('/absolute/path/to/foo.css');
91 |
92 | $this->files->shouldReceive('exists')->once()->with('path/to/public/absolute/path/to/foo.css')->andReturn(false);
93 | $this->files->shouldReceive('exists')->once()->with('/absolute/path/to/foo.css')->andReturn(true);
94 |
95 | $this->assertEquals('/absolute/path/to/foo.css', $this->finder->find('/absolute/path/to/foo.css'));
96 | }
97 |
98 |
99 | public function testFindAliasedAsset()
100 | {
101 | $this->config->shouldReceive('get')->once()->with('basset::aliases.assets.foo', 'foo')->andReturn('foo.css');
102 |
103 | $this->files->shouldReceive('exists')->once()->with('path/to/public/foo.css')->andReturn(true);
104 |
105 | $this->assertEquals('path/to/public/foo.css', $this->finder->find('foo'));
106 | }
107 |
108 |
109 | /**
110 | * @expectedException Basset\Exceptions\DirectoryNotFoundException
111 | */
112 | public function testSettingInvalidWorkingDirectoryThrowsException()
113 | {
114 | $this->files->shouldReceive('exists')->once()->with('path/to/public/working/directory')->andReturn(false);
115 | $this->finder->setWorkingDirectory('working/directory');
116 | }
117 |
118 |
119 | public function testResettingWorkingDirectory()
120 | {
121 | $this->files->shouldReceive('exists')->once()->with('path/to/public/working/directory')->andReturn(true);
122 | $this->finder->setWorkingDirectory('working/directory');
123 | $this->assertEquals('path/to/public/working/directory', $this->finder->getWorkingDirectory());
124 |
125 | $this->finder->resetWorkingDirectory();
126 | $this->assertFalse($this->finder->getWorkingDirectory());
127 | }
128 |
129 |
130 | public function testWorkingDirectoryStackIsPrefixed()
131 | {
132 | $this->files->shouldReceive('exists')->once()->with('path/to/public/working/directory')->andReturn(true);
133 | $this->files->shouldReceive('exists')->once()->with('path/to/public/working/directory/foo/bar/baz')->andReturn(true);
134 |
135 | $this->finder->setWorkingDirectory('working/directory');
136 | $this->assertEquals('path/to/public/working/directory', $this->finder->getWorkingDirectory());
137 |
138 | $this->finder->setWorkingDirectory('foo/bar/baz');
139 | $this->assertEquals('path/to/public/working/directory/foo/bar/baz', $this->finder->getWorkingDirectory());
140 |
141 | $this->finder->resetWorkingDirectory();
142 | $this->assertEquals('path/to/public/working/directory', $this->finder->getWorkingDirectory());
143 |
144 | $this->finder->resetWorkingDirectory();
145 | $this->assertFalse($this->finder->getWorkingDirectory());
146 | }
147 |
148 |
149 | }
--------------------------------------------------------------------------------
/src/Basset/Filter/UriRewriteFilter.php:
--------------------------------------------------------------------------------
1 |
12 | * @license
13 | * @package Minify
14 | * @copyright 2008 Steve Clay / Ryan Grove
15 | */
16 | class UriRewriteFilter implements FilterInterface {
17 |
18 | /**
19 | * Applications document root. This is typically the public directory.
20 | *
21 | * @var string
22 | */
23 | protected $documentRoot;
24 |
25 | /**
26 | * Root directory of the asset.
27 | *
28 | * @var string
29 | */
30 | protected $assetDirectory;
31 |
32 | /**
33 | * Array of symbolic links.
34 | *
35 | * @var array
36 | */
37 | protected $symlinks;
38 |
39 | /**
40 | * Create a new UriRewriteFilter instance.
41 | *
42 | * @param string $documentRoot
43 | * @param array $symlinks
44 | * @return void
45 | */
46 | public function __construct($documentRoot = null, $symlinks = array())
47 | {
48 | $this->documentRoot = $this->realPath($documentRoot ?: $_SERVER['DOCUMENT_ROOT']);
49 | $this->symlinks = $symlinks;
50 | }
51 |
52 | /**
53 | * Apply filter on file load.
54 | *
55 | * @param \Assetic\Asset\AssetInterface $asset
56 | * @return void
57 | */
58 | public function filterLoad(AssetInterface $asset){}
59 |
60 | /**
61 | * Apply a filter on file dump.
62 | *
63 | * @param \Assetic\Asset\AssetInterface $asset
64 | * @return void
65 | */
66 | public function filterDump(AssetInterface $asset)
67 | {
68 | $this->assetDirectory = $this->realPath($asset->getSourceRoot());
69 |
70 | $content = $asset->getContent();
71 |
72 | // Spin through the symlinks and normalize them. We'll first unset the original
73 | // symlink so that it doesn't clash with the new symlinks once they are added
74 | // back in.
75 | foreach ($this->symlinks as $link => $target)
76 | {
77 | unset($this->symlinks[$link]);
78 |
79 | if ($link == '//')
80 | {
81 | $link = $this->documentRoot;
82 | }
83 | else
84 | {
85 | $link = str_replace('//', $this->documentRoot.'/', $link);
86 | }
87 |
88 | $link = strtr($link, '/', DIRECTORY_SEPARATOR);
89 |
90 | $this->symlinks[$link] = $this->realPath($target);
91 | }
92 |
93 | $content = $this->trimUrls($content);
94 |
95 | $content = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/', array($this, 'processUriCallback'), $content);
96 |
97 | $content = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/', array($this, 'processUriCallback'), $content);
98 |
99 | $asset->setContent($content);
100 | }
101 |
102 | /**
103 | * Takes a path and transforms it to a real path.
104 | *
105 | * @param string $path
106 | * @return string
107 | */
108 | protected function realPath($path)
109 | {
110 | if ($realPath = realpath($path))
111 | {
112 | $path = $realPath;
113 | }
114 |
115 | return rtrim($path, '/\\');
116 | }
117 |
118 | /**
119 | * Trims URLs.
120 | *
121 | * @param string $content
122 | * @return string
123 | */
124 | protected function trimUrls($content)
125 | {
126 | return preg_replace('/url\\(\\s*([^\\)]+?)\\s*\\)/x', 'url($1)', $content);
127 | }
128 |
129 | /**
130 | * Processes a regular expression callback, determines the URI and returns the rewritten URIs.
131 | *
132 | * @param array $matches
133 | * @return string
134 | */
135 | protected function processUriCallback($matches)
136 | {
137 | $isImport = $matches[0][0] === '@';
138 |
139 | // Determine what the quote character and the URI is, if there is one.
140 | $quoteCharacter = $uri = null;
141 |
142 | if ($isImport)
143 | {
144 | $quoteCharater = $matches[1];
145 |
146 | $uri = $matches[2];
147 | }
148 | else
149 | {
150 | if ($matches[1][0] === "'" or $matches[1][0] === '"')
151 | {
152 | $quoteCharacter = $matches[1][0];
153 | }
154 |
155 | if ( ! $quoteCharacter)
156 | {
157 | $uri = $matches[1];
158 | }
159 | else
160 | {
161 | $uri = substr($matches[1], 1, strlen($matches[1]) - 2);
162 | }
163 | }
164 |
165 | // Analyze the URI
166 | if ($uri[0] !== '/' and strpos($uri, '//') === false and strpos($uri, 'data') !== 0)
167 | {
168 | $uri = $this->rewriteRelative($uri);
169 | }
170 |
171 | if ($isImport)
172 | {
173 | return "@import {$quoteCharacter}{$uri}{$quoteCharacter}";
174 | }
175 |
176 | return "url({$quoteCharacter}{$uri}{$quoteCharacter})";
177 | }
178 |
179 | /**
180 | * Rewrites a relative URI.
181 | *
182 | * @param string $uri
183 | * @return string
184 | */
185 | protected function rewriteRelative($uri)
186 | {
187 | $path = strtr($this->assetDirectory, '/', DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.strtr($uri, '/', DIRECTORY_SEPARATOR);
188 |
189 | foreach ($this->symlinks as $link => $target)
190 | {
191 | if (strpos($path, $target) === 0)
192 | {
193 | $path = $link.substr($path, strlen($target));
194 |
195 | break;
196 | }
197 | }
198 |
199 | // Strip the document root from the path.
200 | $path = substr($path, strlen($this->documentRoot));
201 |
202 | $uri = strtr($path, '/\\', '//');
203 | $uri = $this->removeDots($uri);
204 |
205 | return $uri;
206 | }
207 |
208 | /**
209 | * Removes dots from a URI.
210 | *
211 | * @param string $uri
212 | * @return string
213 | */
214 | protected function removeDots($uri)
215 | {
216 | $uri = str_replace('/./', '/', $uri);
217 |
218 | do
219 | {
220 | $uri = preg_replace('@/[^/]+/\\.\\./@', '/', $uri, 1, $changed);
221 | }
222 | while ($changed);
223 |
224 | return $uri;
225 | }
226 |
227 | }
--------------------------------------------------------------------------------
/src/Basset/Builder/FilesystemCleaner.php:
--------------------------------------------------------------------------------
1 | environment = $environment;
51 | $this->manifest = $manifest;
52 | $this->files = $files;
53 | $this->buildPath = $buildPath;
54 | }
55 |
56 | /**
57 | * Clean all built collections and the manifest entries.
58 | *
59 | * @return void
60 | */
61 | public function cleanAll()
62 | {
63 | $collections = array_keys($this->environment->all()) + array_keys($this->manifest->all());
64 |
65 | foreach ($collections as $collection)
66 | {
67 | $this->clean($collection);
68 | }
69 | }
70 |
71 | /**
72 | * Cleans a built collection and the manifest entries.
73 | *
74 | * @param string $collection
75 | * @return void
76 | */
77 | public function clean($collection)
78 | {
79 | $entry = $this->manifest->get($collection);
80 |
81 | // If the collection exists on the environment then we'll proceed with cleaning the filesystem
82 | // This removes any double-up production and development builds.
83 | if (isset($this->environment[$collection]))
84 | {
85 | $this->cleanFilesystem($this->environment[$collection], $entry);
86 | }
87 |
88 | // If the collection does not exist on the environment then we'll instrcut the manifest to
89 | // forget this collection.
90 | else
91 | {
92 | $this->manifest->forget($collection);
93 | }
94 |
95 | // Cleaning the manifest is important as it will also remove unnecessary files from the
96 | // filesystem if a collection has been removed.
97 | $this->cleanManifestFiles($collection, $entry);
98 |
99 | $this->manifest->save();
100 | }
101 |
102 | /**
103 | * Cleans a built collections files removing any outdated builds.
104 | *
105 | * @param \Basset\Collection $collection
106 | * @param \Basset\Manifest\Entry $entry
107 | * @return void
108 | */
109 | protected function cleanFilesystem(Collection $collection, Entry $entry)
110 | {
111 | $this->cleanProductionFiles($collection, $entry);
112 |
113 | $this->cleanDevelopmentFiles($collection, $entry);
114 | }
115 |
116 | /**
117 | * Clean the collections manifest entry files.
118 | *
119 | * @param string $collection
120 | * @param \Basset\Manifest\Entry $entry
121 | * @return void
122 | */
123 | protected function cleanManifestFiles($collection, Entry $entry)
124 | {
125 | if ( ! $entry->hasProductionFingerprints() or ! isset($this->environment[$collection]))
126 | {
127 | $this->deleteMatchingFiles($this->buildPath.'/'.$collection.'-*.*');
128 |
129 | $entry->resetProductionFingerprints();
130 | }
131 |
132 | if ( ! $entry->hasDevelopmentAssets() or ! isset($this->environment[$collection]))
133 | {
134 | $this->files->deleteDirectory($this->buildPath.'/'.$collection);
135 |
136 | $entry->resetDevelopmentAssets();
137 | }
138 | }
139 |
140 | /**
141 | * Clean collection production files.
142 | *
143 | * @param \Basset\Collection $collection
144 | * @param \Basset\Manifest\Entry $entry
145 | * @return void
146 | */
147 | protected function cleanProductionFiles(Collection $collection, Entry $entry)
148 | {
149 | foreach ($entry->getProductionFingerprints() as $fingerprint)
150 | {
151 | $wildcardPath = $this->replaceFingerprintWithWildcard($fingerprint);
152 |
153 | $this->deleteMatchingFiles($this->buildPath.'/'.$wildcardPath, $fingerprint);
154 | }
155 | }
156 |
157 | /**
158 | * Clean collection development files.
159 | *
160 | * @param \Basset\Collection $collection
161 | * @param \Basset\Manifest\Entry $entry
162 | * @return void
163 | */
164 | protected function cleanDevelopmentFiles(Collection $collection, Entry $entry)
165 | {
166 | foreach ($entry->getDevelopmentAssets() as $assets)
167 | {
168 | foreach ($assets as $asset)
169 | {
170 | $wildcardPath = $this->replaceFingerprintWithWildcard($asset);
171 |
172 | $this->deleteMatchingFiles($this->buildPath.'/'.$collection->getIdentifier().'/'.$wildcardPath, array_values($assets));
173 | }
174 | }
175 | }
176 |
177 | /**
178 | * Delete matching files from the wildcard glob search except the ignored file.
179 | *
180 | * @param string $wildcard
181 | * @param array|string $ignored
182 | * @return void
183 | */
184 | protected function deleteMatchingFiles($wildcard, $ignored = null)
185 | {
186 | if (is_array($files = $this->files->glob($wildcard)))
187 | {
188 | foreach ($files as $path)
189 | {
190 | if ( ! is_null($ignored))
191 | {
192 | // Spin through each of the ignored assets and if the current file path ends
193 | // with any of the ignored asset paths then we'll skip this asset as it
194 | // needs to be kept.
195 | foreach ((array) $ignored as $ignore)
196 | {
197 | if (ends_with($path, $ignore)) continue 2;
198 | }
199 | }
200 |
201 | $this->files->delete($path);
202 | }
203 | }
204 |
205 | }
206 |
207 | /**
208 | * Replace a fingerprint with a wildcard.
209 | *
210 | * @param string $value
211 | * @return string
212 | */
213 | protected function replaceFingerprintWithWildcard($value)
214 | {
215 | return preg_replace('/(.*?)-([\w\d]{32})\.(.*?)/', '$1-*.$3', $value);
216 | }
217 |
218 | }
--------------------------------------------------------------------------------
/src/Basset/AssetFinder.php:
--------------------------------------------------------------------------------
1 | files = $files;
50 | $this->config = $config;
51 | $this->publicPath = $publicPath;
52 | }
53 |
54 | /**
55 | * Find and return an assets path.
56 | *
57 | * @param string $name
58 | * @return string
59 | * @throws \Basset\Exceptions\AssetExistsException
60 | * @throws \Basset\Exceptions\AssetNotFoundException
61 | */
62 | public function find($name)
63 | {
64 | $name = $this->config->get("basset::aliases.assets.{$name}", $name);
65 |
66 | // Spin through an array of methods ordered by the priority of how an asset should be found.
67 | // Once we find a non-null path we'll return that path breaking from the loop.
68 | foreach (array('RemotelyHosted', 'PackageAsset', 'WorkingDirectory', 'PublicPath', 'AbsolutePath') as $method)
69 | {
70 | if ($path = $this->{'find'.$method}($name))
71 | {
72 | return $path;
73 | }
74 | }
75 |
76 | throw new AssetNotFoundException;
77 | }
78 |
79 | /**
80 | * Find a remotely hosted asset.
81 | *
82 | * @param string $name
83 | * @return null|string
84 | */
85 | public function findRemotelyHosted($name)
86 | {
87 | if (filter_var($name, FILTER_VALIDATE_URL) or starts_with($name, '//'))
88 | {
89 | return $name;
90 | }
91 | }
92 |
93 | /**
94 | * Find an asset by looking for a prefixed package namespace.
95 | *
96 | * @param string $name
97 | * @return null|string
98 | */
99 | public function findPackageAsset($name)
100 | {
101 | if (str_contains($name, '::'))
102 | {
103 | list($namespace, $name) = explode('::', $name);
104 |
105 | if ( ! isset($this->hints[$namespace]))
106 | {
107 | return;
108 | }
109 |
110 | $path = $this->prefixPublicPath('packages/'.$this->hints[$namespace].'/'.$name);
111 |
112 | if ($this->files->exists($path))
113 | {
114 | return $path;
115 | }
116 | }
117 | }
118 |
119 | /**
120 | * Find an asset by searching in the current working directory.
121 | *
122 | * @param string $name
123 | * @return null|string
124 | */
125 | public function findWorkingDirectory($name)
126 | {
127 | $path = $this->getWorkingDirectory().'/'.$name;
128 |
129 | if ($this->withinWorkingDirectory() and $this->files->exists($path))
130 | {
131 | return $path;
132 | }
133 | }
134 |
135 | /**
136 | * Find an asset by searching in the public path.
137 | *
138 | * @param string $name
139 | * @return null|string
140 | */
141 | public function findPublicPath($name)
142 | {
143 | $path = $this->prefixPublicPath($name);
144 |
145 | if ($this->files->exists($path))
146 | {
147 | return $path;
148 | }
149 | }
150 |
151 | /**
152 | * Find an asset by absolute path.
153 | *
154 | * @param string $name
155 | * @return null|string
156 | */
157 | public function findAbsolutePath($name)
158 | {
159 | if ($this->files->exists($name))
160 | {
161 | return $name;
162 | }
163 | }
164 |
165 | /**
166 | * Set the working directory path.
167 | *
168 | * @param string $path
169 | * @return string
170 | * @throws \Basset\Exceptions\DirectoryNotFoundException
171 | */
172 | public function setWorkingDirectory($path)
173 | {
174 | $path = $this->prefixDirectoryStack($path);
175 |
176 | if ($this->files->exists($path))
177 | {
178 | return $this->directoryStack[] = $path;
179 | }
180 |
181 | throw new DirectoryNotFoundException("Directory [{$path}] could not be found.");
182 | }
183 |
184 | /**
185 | * Pop the last directory from the directory stack.
186 | *
187 | * @return string
188 | */
189 | public function resetWorkingDirectory()
190 | {
191 | return array_pop($this->directoryStack);
192 | }
193 |
194 | /**
195 | * Determine if within a working directory.
196 | *
197 | * @return bool
198 | */
199 | public function withinWorkingDirectory()
200 | {
201 | return ! empty($this->directoryStack);
202 | }
203 |
204 | /**
205 | * Get the last working directory path off the directory stack.
206 | *
207 | * @return string
208 | */
209 | public function getWorkingDirectory()
210 | {
211 | return end($this->directoryStack);
212 | }
213 |
214 | /**
215 | * Get the working directory stack.
216 | *
217 | * @return array
218 | */
219 | public function getDirectoryStack()
220 | {
221 | return $this->directoryStack;
222 | }
223 |
224 | /**
225 | * Prefix the last directory from the stack or the public path if not
226 | * within a working directory
227 | *
228 | * @param string $path
229 | * @return string
230 | */
231 | public function prefixDirectoryStack($path)
232 | {
233 | if ($this->withinWorkingDirectory())
234 | {
235 | return rtrim($this->getWorkingDirectory().'/'.ltrim($path, '/'), '/');
236 | }
237 |
238 | return $this->prefixPublicPath($path);
239 | }
240 |
241 | /**
242 | * Add a package namespace.
243 | *
244 | * @param string $namespace
245 | * @param string $package
246 | * @return void
247 | */
248 | public function addNamespace($namespace, $package)
249 | {
250 | $this->hints[$namespace] = $package;
251 | }
252 |
253 | /**
254 | * Prefix the public path to a path.
255 | *
256 | * @param string $path
257 | * @return string
258 | */
259 | protected function prefixPublicPath($path)
260 | {
261 | return rtrim($this->publicPath.'/'.ltrim($path, '/'), '/');
262 | }
263 |
264 | /**
265 | * Get the public path.
266 | *
267 | * @return string
268 | */
269 | public function getPublicPath()
270 | {
271 | return $this->publicPath;
272 | }
273 |
274 | }
--------------------------------------------------------------------------------
/src/Basset/BassetServiceProvider.php:
--------------------------------------------------------------------------------
1 | package('jasonlewis/basset', 'basset', __DIR__.'/../');
54 |
55 | // Tell the logger to use a rotating files setup to log problems encountered during
56 | // Bassets operation but only when debugging is enabled.
57 | if ($this->app['config']->get('basset::debug', false))
58 | {
59 | $this->app['basset.log']->useDailyFiles($this->app['path.storage'].'/logs/basset.txt', 0, 'warning');
60 | }
61 |
62 | // If debugging is disabled we'll use a null handler to essentially send all logged
63 | // messages into a blackhole.
64 | else
65 | {
66 | $handler = new NullHandler(MonologLogger::WARNING);
67 |
68 | $this->app['basset.log']->getMonolog()->pushHandler($handler);
69 | }
70 |
71 | $this->app->instance('basset.path.build', $this->app['path.public'].'/'.$this->app['config']->get('basset::build_path'));
72 |
73 | $this->registerBladeExtensions();
74 |
75 | // Collections can be defined as an array in the configuration file. We'll register
76 | // this array of collections with the environment.
77 | $this->app['basset']->collections($this->app['config']->get('basset::collections'));
78 |
79 | // Load the local manifest that contains the fingerprinted paths to both production
80 | // and development builds.
81 | $this->app['basset.manifest']->load();
82 | }
83 |
84 | /**
85 | * Register the Blade extensions with the compiler.
86 | *
87 | * @return void
88 | */
89 | protected function registerBladeExtensions()
90 | {
91 | $blade = $this->app['view']->getEngineResolver()->resolve('blade')->getCompiler();
92 |
93 | $blade->extend(function($value, $compiler)
94 | {
95 | $matcher = $compiler->createMatcher('javascripts');
96 |
97 | return preg_replace($matcher, '$1', $value);
98 | });
99 |
100 | $blade->extend(function($value, $compiler)
101 | {
102 | $matcher = $compiler->createMatcher('stylesheets');
103 |
104 | return preg_replace($matcher, '$1', $value);
105 | });
106 |
107 | $blade->extend(function($value, $compiler)
108 | {
109 | $matcher = $compiler->createMatcher('assets');
110 |
111 | return preg_replace($matcher, '$1', $value);
112 | });
113 | }
114 |
115 | /**
116 | * Register the service provider.
117 | *
118 | * @return void
119 | */
120 | public function register()
121 | {
122 | foreach ($this->components as $component)
123 | {
124 | $this->{'register'.$component}();
125 | }
126 | }
127 |
128 | /**
129 | * Register the asset finder.
130 | *
131 | * @return void
132 | */
133 | protected function registerAssetFinder()
134 | {
135 | $this->app['basset.finder'] = $this->app->share(function($app)
136 | {
137 | return new AssetFinder($app['files'], $app['config'], $app['path.public']);
138 | });
139 | }
140 |
141 | /**
142 | * Register the collection server.
143 | *
144 | * @return void
145 | */
146 | protected function registerServer()
147 | {
148 | $this->app['basset.server'] = $this->app->share(function($app)
149 | {
150 | return new Server($app);
151 | });
152 | }
153 |
154 | /**
155 | * Register the logger.
156 | *
157 | * @return void
158 | */
159 | protected function registerLogger()
160 | {
161 | $this->app['basset.log'] = $this->app->share(function($app)
162 | {
163 | return new Writer(new \Monolog\Logger('basset'), $app['events']);
164 | });
165 | }
166 |
167 | /**
168 | * Register the factory manager.
169 | *
170 | * @return void
171 | */
172 | protected function registerFactoryManager()
173 | {
174 | $this->app['basset.factory'] = $this->app->share(function($app)
175 | {
176 | return new FactoryManager($app);
177 | });
178 | }
179 |
180 | /**
181 | * Register the collection repository.
182 | *
183 | * @return void
184 | */
185 | protected function registerManifest()
186 | {
187 | $this->app['basset.manifest'] = $this->app->share(function($app)
188 | {
189 | $meta = $app['config']->get('app.manifest');
190 |
191 | return new Manifest($app['files'], $meta);
192 | });
193 | }
194 |
195 | /**
196 | * Register the collection builder.
197 | *
198 | * @return void
199 | */
200 | protected function registerBuilder()
201 | {
202 | $this->app['basset.builder'] = $this->app->share(function($app)
203 | {
204 | return new Builder($app['files'], $app['basset.manifest'], $app['basset.path.build']);
205 | });
206 |
207 | $this->app['basset.builder.cleaner'] = $this->app->share(function($app)
208 | {
209 | return new FilesystemCleaner($app['basset'], $app['basset.manifest'], $app['files'], $app['basset.path.build']);
210 | });
211 | }
212 |
213 | /**
214 | * Register the basset environment.
215 | *
216 | * @return void
217 | */
218 | protected function registerBasset()
219 | {
220 | $this->app['basset'] = $this->app->share(function($app)
221 | {
222 | return new Environment($app['basset.factory'], $app['basset.finder']);
223 | });
224 | }
225 |
226 | /**
227 | * Register the commands.
228 | *
229 | * @return void
230 | */
231 | public function registerCommands()
232 | {
233 | $this->registerBassetCommand();
234 |
235 | $this->registerBuildCommand();
236 |
237 | $this->commands('command.basset', 'command.basset.build');
238 | }
239 |
240 | /**
241 | * Register the basset command.
242 | *
243 | * @return void
244 | */
245 | protected function registerBassetCommand()
246 | {
247 | $this->app['command.basset'] = $this->app->share(function($app)
248 | {
249 | return new BassetCommand($app['basset.manifest'], $app['basset'], $app['basset.builder.cleaner']);
250 | });
251 | }
252 |
253 | /**
254 | * Register the build command.
255 | *
256 | * @return void
257 | */
258 | protected function registerBuildCommand()
259 | {
260 | $this->app['command.basset.build'] = $this->app->share(function($app)
261 | {
262 | return new BuildCommand($app['basset'], $app['basset.builder'], $app['basset.builder.cleaner']);
263 | });
264 | }
265 |
266 | }
--------------------------------------------------------------------------------
/src/Basset/Builder/Builder.php:
--------------------------------------------------------------------------------
1 | files = $files;
57 | $this->manifest = $manifest;
58 | $this->buildPath = $buildPath;
59 |
60 | $this->makeBuildPath();
61 | }
62 |
63 | /**
64 | * Build a production collection.
65 | *
66 | * @param \Basset\Collection $collection
67 | * @param string $group
68 | * @return void
69 | * @throws \Basset\Exceptions\BuildNotRequiredException
70 | */
71 | public function buildAsProduction(Collection $collection, $group)
72 | {
73 | // Get the assets of the given group from the collection. The collection is also responsible
74 | // for handling any ordering of the assets so that we just need to build them.
75 | $assets = $collection->getAssetsWithoutRaw($group);
76 |
77 | $entry = $this->manifest->make($identifier = $collection->getIdentifier());
78 |
79 | // Build the assets and transform the array into a newline separated string. We'll use this
80 | // as a basis for the collections fingerprint and it will decide as to whether the
81 | // collection needs to be rebuilt.
82 | $build = array_to_newlines($assets->map(function($asset) { return $asset->build(true); })->all());
83 |
84 | // If the build is empty then we'll reset the fingerprint on the manifest entry and throw the
85 | // exception as there's no point going any further.
86 | if (empty($build))
87 | {
88 | $entry->resetProductionFingerprint($group);
89 |
90 | throw new BuildNotRequiredException;
91 | }
92 |
93 | $fingerprint = $identifier.'-'.md5($build).'.'.$collection->getExtension($group);
94 |
95 | $path = $this->buildPath.'/'.$fingerprint;
96 |
97 | // If the collection has already been built and we're not forcing the build then we'll throw
98 | // the exception here as we don't need to rebuild the collection.
99 | if ($fingerprint == $entry->getProductionFingerprint($group) and ! $this->force and $this->files->exists($path))
100 | {
101 | throw new BuildNotRequiredException;
102 | }
103 | else
104 | {
105 | $this->files->put($path, $this->gzip($build));
106 |
107 | $entry->setProductionFingerprint($group, $fingerprint);
108 | }
109 | }
110 |
111 | /**
112 | * Build a development collection.
113 | *
114 | * @param \Basset\Collection $collection
115 | * @param string $group
116 | * @return void
117 | * @throws \Basset\Exceptions\BuildNotRequiredException
118 | */
119 | public function buildAsDevelopment(Collection $collection, $group)
120 | {
121 | // Get the assets of the given group from the collection. The collection is also responsible
122 | // for handling any ordering of the assets so that we just need to build them.
123 | $assets = $collection->getAssetsWithoutRaw($group);
124 |
125 | $entry = $this->manifest->make($identifier = $collection->getIdentifier());
126 |
127 | // If the collection definition has changed when compared to the manifest entry or if the
128 | // collection is being forcefully rebuilt then we'll reset the development assets.
129 | if ($this->collectionDefinitionHasChanged($assets, $entry, $group) or $this->force)
130 | {
131 | $entry->resetDevelopmentAssets($group);
132 | }
133 |
134 | // Otherwise we'll look at each of the assets and see if the entry has the asset or if
135 | // the assets build path differs from that of the manifest entry.
136 | else
137 | {
138 | $assets = $assets->filter(function($asset) use ($entry)
139 | {
140 | return ! $entry->hasDevelopmentAsset($asset) or $asset->getBuildPath() != $entry->getDevelopmentAsset($asset);
141 | });
142 | }
143 |
144 | if ( ! $assets->isEmpty())
145 | {
146 | foreach ($assets as $asset)
147 | {
148 | $path = "{$this->buildPath}/{$identifier}/{$asset->getBuildPath()}";
149 |
150 | // If the build directory does not exist we'll attempt to recursively create it so we can
151 | // build the asset to the directory.
152 | ! $this->files->exists($directory = dirname($path)) and $this->files->makeDirectory($directory, 0777, true);
153 |
154 | $this->files->put($path, $this->gzip($asset->build()));
155 |
156 | // Add the development asset to the manifest entry so that we can save the built asset
157 | // to the manifest.
158 | $entry->addDevelopmentAsset($asset);
159 | }
160 | }
161 | else
162 | {
163 | throw new BuildNotRequiredException;
164 | }
165 | }
166 |
167 | /**
168 | * Determine if the collections definition has changed when compared to the manifest.
169 | *
170 | * @param \Illuminate\Support\Collection $assets
171 | * @param \Basset\Manifest\Entry $entry
172 | * @param string $group
173 | * @return bool
174 | */
175 | protected function collectionDefinitionHasChanged($assets, $entry, $group)
176 | {
177 | // If the manifest entry doesn't even have the group registered then it's obvious that the
178 | // collection has changed and needs to be rebuilt.
179 | if ( ! $entry->hasDevelopmentAssets($group))
180 | {
181 | return true;
182 | }
183 |
184 | // Get the development assets from the manifest entry and flatten the keys so that we have
185 | // an array of relative paths that we can compare from.
186 | $manifest = $entry->getDevelopmentAssets($group);
187 |
188 | $manifest = array_flatten(array_keys($manifest));
189 |
190 | // Compute the difference between the collections assets and the manifests assets. If we get
191 | // an array of values then the collection has changed since the last build and everything
192 | // should be rebuilt.
193 | $difference = array_diff_assoc($manifest, $assets->map(function($asset) { return $asset->getRelativePath(); })->flatten()->toArray());
194 |
195 | return ! empty($difference);
196 | }
197 |
198 | /**
199 | * Make the build path if it does not exist.
200 | *
201 | * @return void
202 | */
203 | protected function makeBuildPath()
204 | {
205 | if ( ! $this->files->exists($this->buildPath))
206 | {
207 | $this->files->makeDirectory($this->buildPath);
208 | }
209 | }
210 |
211 | /**
212 | * If Gzipping is enabled the the zlib extension is loaded we'll Gzip the contents
213 | * with a maximum compression level of 9.
214 | *
215 | * @param string $contents
216 | * @return string
217 | */
218 | protected function gzip($contents)
219 | {
220 | if ($this->gzip and function_exists('gzencode'))
221 | {
222 | return gzencode($contents, 9);
223 | }
224 |
225 | return $contents;
226 | }
227 |
228 | /**
229 | * Set built collections to be gzipped.
230 | *
231 | * @param bool $gzip
232 | * @return \Basset\Builder\Builder
233 | */
234 | public function setGzip($gzip)
235 | {
236 | $this->gzip = $gzip;
237 |
238 | return $this;
239 | }
240 |
241 | /**
242 | * Set the building to be forced.
243 | *
244 | * @param bool $force
245 | * @return \Basset\Builder\Builder
246 | */
247 | public function setForce($force)
248 | {
249 | $this->force = $force;
250 |
251 | return $this;
252 | }
253 |
254 | }
255 |
--------------------------------------------------------------------------------
/src/Basset/Server.php:
--------------------------------------------------------------------------------
1 | app = $app;
26 | }
27 |
28 | /**
29 | * Serve a collection where the group is determined by the the extension.
30 | *
31 | * @param string $collection
32 | * @param string $format
33 | * @return string
34 | */
35 | public function collection($collection, $format = null)
36 | {
37 | list($collection, $extension) = preg_split('/\.(css|js)/', $collection, 2, PREG_SPLIT_DELIM_CAPTURE);
38 |
39 | $group = $extension == 'css' ? 'stylesheets' : 'javascripts';
40 |
41 | return $this->serve($collection, $group, $format);
42 | }
43 |
44 | /**
45 | * Serve the stylesheets for a given collection.
46 | *
47 | * @param string $collection
48 | * @param string $format
49 | * @return string
50 | */
51 | public function stylesheets($collection, $format = null)
52 | {
53 | return $this->serve($collection, 'stylesheets', $format);
54 | }
55 |
56 | /**
57 | * Serve the javascripts for a given collection.
58 | *
59 | * @param string $collection
60 | * @param string $format
61 | * @return string
62 | */
63 | public function javascripts($collection, $format = null)
64 | {
65 | return $this->serve($collection, 'javascripts', $format);
66 | }
67 |
68 | /**
69 | * Serve a given group for a collection.
70 | *
71 | * @param string $collection
72 | * @param string $group
73 | * @param string $format
74 | * @return string
75 | */
76 | public function serve($collection, $group, $format = null)
77 | {
78 | if ( ! isset($this->app['basset'][$collection]))
79 | {
80 | return '';
81 | }
82 |
83 | // Get the collection instance from the array of collections. This instance will be used
84 | // throughout the building process to fetch assets and compare against the stored
85 | // manfiest of fingerprints.
86 | $collection = $this->app['basset'][$collection];
87 |
88 | if ($this->runningInProduction() and $this->app['basset.manifest']->has($collection))
89 | {
90 | if ($this->app['basset.manifest']->get($collection)->hasProductionFingerprint($group))
91 | {
92 | return $this->serveProductionCollection($collection, $group, $format);
93 | }
94 | }
95 |
96 | return $this->serveDevelopmentCollection($collection, $group, $format);
97 | }
98 |
99 | /**
100 | * Serve a production collection.
101 | *
102 | * @param \Basset\Collection $collection
103 | * @param string $group
104 | * @param string $format
105 | * @return array
106 | */
107 | protected function serveProductionCollection(Collection $collection, $group, $format)
108 | {
109 | $entry = $this->getCollectionEntry($collection);
110 |
111 | $fingerprint = $entry->getProductionFingerprint($group);
112 |
113 | $production = $this->{'create'.studly_case($group).'Element'}($this->prefixBuildPath($fingerprint), $format);
114 |
115 | return $this->formatResponse($this->serveRawAssets($collection, $group, $format), $production);
116 | }
117 |
118 | /**
119 | * Serve a development collection.
120 | *
121 | * @param \Basset\Collection $collection
122 | * @param string $group
123 | * @param string $format
124 | * @return array
125 | */
126 | protected function serveDevelopmentCollection(Collection $collection, $group, $format)
127 | {
128 | $identifier = $collection->getIdentifier();
129 |
130 | // Before we fetch the collections manifest entry we'll try to build the collection
131 | // again if there is anything outstanding. This doesn't have a huge impact on
132 | // page loads time like trying to dynamically serve each asset.
133 | $this->tryDevelopmentBuild($collection, $group);
134 |
135 | $entry = $this->getCollectionEntry($collection);
136 |
137 | $responses = array();
138 |
139 | foreach ($collection->getAssetsWithRaw($group) as $asset)
140 | {
141 | if ( ! $asset->isRaw() and $path = $entry->getDevelopmentAsset($asset))
142 | {
143 | $path = $this->prefixBuildPath($identifier.'/'.$path);
144 | }
145 | else
146 | {
147 | $path = $asset->getRelativePath();
148 | }
149 |
150 | $responses[] = $this->{'create'.studly_case($group).'Element'}($path, $format);
151 | }
152 |
153 | return $this->formatResponse($responses);
154 | }
155 |
156 | /**
157 | * Serve a collections raw assets.
158 | *
159 | * @param \Basset\Collection $collection
160 | * @param string $group
161 | * @param string $format
162 | * @return array
163 | */
164 | protected function serveRawAssets(Collection $collection, $group, $format)
165 | {
166 | $responses = array();
167 |
168 | foreach ($collection->getAssetsOnlyRaw($group) as $asset)
169 | {
170 | $path = $asset->getRelativePath();
171 |
172 | $responses[] = $this->{'create'.studly_case($group).'Element'}($path, $format);
173 | }
174 |
175 | return $responses;
176 | }
177 |
178 | /**
179 | * Format an array of responses and return a string.
180 | *
181 | * @param mixed $args
182 | * @return string
183 | */
184 | protected function formatResponse()
185 | {
186 | $responses = array();
187 |
188 | foreach (func_get_args() as $response)
189 | {
190 | $responses = array_merge($responses, (array) $response);
191 | }
192 |
193 | return array_to_newlines($responses);
194 | }
195 |
196 | /**
197 | * Get a collection manifest entry.
198 | *
199 | * @param \Basset\Collection $collection
200 | * @return \Basset\Manifest\Entry
201 | */
202 | protected function getCollectionEntry(Collection $collection)
203 | {
204 | return $this->app['basset.manifest']->get($collection);
205 | }
206 |
207 | /**
208 | * Try the development build of a collection.
209 | *
210 | * @param \Basset\Collection $collection
211 | * @param string $group
212 | * @return void
213 | */
214 | protected function tryDevelopmentBuild(Collection $collection, $group)
215 | {
216 | try
217 | {
218 | $this->app['basset.builder']->buildAsDevelopment($collection, $group);
219 | }
220 | catch (BuildNotRequiredException $e) {}
221 |
222 | $this->app['basset.builder.cleaner']->clean($collection->getIdentifier());
223 | }
224 |
225 | /**
226 | * Prefix the build path to a given path.
227 | *
228 | * @param string $path
229 | * @return string
230 | */
231 | protected function prefixBuildPath($path)
232 | {
233 | if ($buildPath = $this->app['config']->get('basset::build_path'))
234 | {
235 | $path = "{$buildPath}/{$path}";
236 | }
237 |
238 | return $path;
239 | }
240 |
241 | /**
242 | * Determine if the application is running in production mode.
243 | *
244 | * @return bool
245 | */
246 | protected function runningInProduction()
247 | {
248 | return in_array($this->app['env'], (array) $this->app['config']->get('basset::production'));
249 | }
250 |
251 | /**
252 | * Create a stylesheets element for the specified path.
253 | *
254 | * @param string $path
255 | * @param string $format
256 | * @return string
257 | */
258 | protected function createStylesheetsElement($path, $format)
259 | {
260 | return sprintf($format ?: '', $this->buildAssetUrl($path));
261 | }
262 |
263 | /**
264 | * Create a javascripts element for the specified path.
265 | *
266 | * @param string $path
267 | * @param string $format
268 | * @return string
269 | */
270 | protected function createJavascriptsElement($path, $format)
271 | {
272 | return sprintf($format ?: '', $this->buildAssetUrl($path));
273 | }
274 |
275 | /**
276 | * Build the URL to an asset.
277 | *
278 | * @param string $path
279 | * @return string
280 | */
281 | public function buildAssetUrl($path)
282 | {
283 | return starts_with($path, '//') ? $path : $this->app['url']->asset($path);
284 | }
285 |
286 | }
287 |
--------------------------------------------------------------------------------
/tests/Basset/AssetTest.php:
--------------------------------------------------------------------------------
1 | files = m::mock('Illuminate\Filesystem\Filesystem');
19 | $this->factory = m::mock('Basset\Factory\FactoryManager');
20 | $this->log = m::mock('Illuminate\Log\Writer');
21 | $this->filter = m::mock('Basset\Factory\FilterFactory', array(array(), array(), 'testing'))->shouldDeferMissing();
22 |
23 | $this->factory->shouldReceive('get')->with('filter')->andReturn($this->filter);
24 |
25 | $this->files->shouldReceive('lastModified')->with('path/to/public/foo/bar.sass')->andReturn('1368422603');
26 |
27 | $this->asset = new Asset($this->files, $this->factory, 'testing', 'path/to/public/foo/bar.sass', 'foo/bar.sass');
28 | $this->asset->setOrder(1);
29 | $this->asset->setGroup('stylesheets');
30 | }
31 |
32 |
33 | public function testGetAssetProperties()
34 | {
35 | $this->assertEquals('foo/bar.sass', $this->asset->getRelativePath());
36 | $this->assertEquals('path/to/public/foo/bar.sass', $this->asset->getAbsolutePath());
37 | $this->assertEquals('foo/bar-2a4bdbebcbf798cb0b59078d98136e3d.css', $this->asset->getBuildPath());
38 | $this->assertEquals('css', $this->asset->getBuildExtension());
39 | $this->assertInstanceOf('Illuminate\Support\Collection', $this->asset->getFilters());
40 | $this->assertEquals('stylesheets', $this->asset->getGroup());
41 | $this->assertEquals('1368422603', $this->asset->getLastModified());
42 | }
43 |
44 |
45 | public function testAssetsCanBeServedRaw()
46 | {
47 | $this->asset->raw();
48 | $this->assertTrue($this->asset->isRaw());
49 | }
50 |
51 |
52 | public function testCheckingOfAssetGroup()
53 | {
54 | $this->assertTrue($this->asset->isStylesheet());
55 | $this->assertFalse($this->asset->isJavascript());
56 | }
57 |
58 |
59 | public function testCheckingOfAssetGroupWhenNoGroupSupplied()
60 | {
61 | $this->asset->setGroup(null);
62 | $this->assertTrue($this->asset->isStylesheet());
63 | }
64 |
65 |
66 | public function testAssetCanBeRemotelyHosted()
67 | {
68 | $asset = new Asset($this->files, $this->factory, 'testing', 'http://foo.com/bar.css', 'http://foo.com/bar.css');
69 |
70 | $this->assertTrue($asset->isRemote());
71 | }
72 |
73 |
74 | public function testAssetCanBeRemotelyHostedWithRelativeProtocol()
75 | {
76 | $asset = new Asset($this->files, $this->factory, 'testing', '//foo.com/bar.css', '//foo.com/bar.css');
77 |
78 | $this->assertTrue($asset->isRemote());
79 | }
80 |
81 |
82 | public function testSettingCustomOrderOfAsset()
83 | {
84 | $this->asset->first();
85 | $this->assertEquals(1, $this->asset->getOrder());
86 |
87 | $this->asset->second();
88 | $this->assertEquals(2, $this->asset->getOrder());
89 |
90 | $this->asset->third();
91 | $this->assertEquals(3, $this->asset->getOrder());
92 |
93 | $this->asset->order(10);
94 | $this->assertEquals(10, $this->asset->getOrder());
95 | }
96 |
97 |
98 | public function testFiltersAreAppliedToAssets()
99 | {
100 | $this->filter->shouldReceive('make')->once()->with('FooFilter')->andReturn($filter = m::mock('Basset\Filter\Filter'));
101 |
102 | $filter->shouldReceive('setResource')->once()->with($this->asset)->andReturn(m::self());
103 | $filter->shouldReceive('getFilter')->once()->andReturn('FooFilter');
104 |
105 | $this->asset->apply('FooFilter');
106 |
107 | $filters = $this->asset->getFilters();
108 |
109 | $this->assertArrayHasKey('FooFilter', $filters->all());
110 | $this->assertInstanceOf('Basset\Filter\Filter', $filters['FooFilter']);
111 | }
112 |
113 |
114 | public function testArrayOfFiltersAreAppliedToAssets()
115 | {
116 | $this->filter->shouldReceive('make')->once()->with('FooFilter')->andReturn($filter = m::mock('Basset\Filter\Filter'));
117 | $filter->shouldReceive('setResource')->once()->with($this->asset)->andReturn(m::self());
118 | $filter->shouldReceive('getFilter')->once()->andReturn('FooFilter');
119 |
120 | $this->filter->shouldReceive('make')->once()->with('BarFilter')->andReturn($filter = m::mock('Basset\Filter\Filter'));
121 | $filter->shouldReceive('setResource')->once()->with($this->asset)->andReturn(m::self());
122 | $filter->shouldReceive('getFilter')->once()->andReturn('BarFilter');
123 |
124 | $this->asset->apply(array('FooFilter', 'BarFilter'));
125 |
126 | $filters = $this->asset->getFilters();
127 |
128 | $this->assertArrayHasKey('FooFilter', $filters->all());
129 | $this->assertArrayHasKey('BarFilter', $filters->all());
130 | }
131 |
132 |
133 | public function testArrayOfFiltersWithCallbacksAreAppliedToAssets()
134 | {
135 | $this->filter->shouldReceive('make')->once()->with('FooFilter')->andReturn($filter = m::mock('Basset\Filter\Filter'));
136 | $filter->shouldReceive('setResource')->once()->with($this->asset)->andReturn(m::self());
137 | $filter->shouldReceive('getFilter')->once()->andReturn('FooFilter');
138 |
139 | $this->asset->apply(array('FooFilter' => function($filter)
140 | {
141 | $filter->applied = true;
142 | }));
143 |
144 | $this->assertTrue($filter->applied);
145 | }
146 |
147 |
148 | public function testFiltersArePreparedCorrectly()
149 | {
150 | $fooFilter = m::mock('Basset\Filter\Filter', array(m::mock('Illuminate\Log\Writer'), 'FooFilter', array(), 'testing'))->shouldDeferMissing();
151 | $fooFilterInstance = m::mock('stdClass, Assetic\Filter\FilterInterface');
152 | $fooFilter->shouldReceive('getClassName')->once()->andReturn($fooFilterInstance);
153 |
154 | $barFilter = m::mock('Basset\Filter\Filter', array($barLog = m::mock('Illuminate\Log\Writer'), 'BarFilter', array(), 'testing'))->shouldDeferMissing();
155 | $barFilter->shouldReceive('getClassName')->once()->andReturn(m::mock('stdClass, Assetic\Filter\FilterInterface'));
156 |
157 | $bazFilter = m::mock('Basset\Filter\Filter', array($bazLog = m::mock('Illuminate\Log\Writer'), 'BazFilter', array(), 'testing'))->shouldDeferMissing();
158 | $bazFilter->shouldReceive('getClassName')->once()->andReturn(m::mock('stdClass, Assetic\Filter\FilterInterface'));
159 |
160 | $quxFilter = m::mock('Basset\Filter\Filter', array($quxLog = m::mock('Illuminate\Log\Writer'), 'QuxFilter', array(), 'testing'))->shouldDeferMissing();
161 | $quxFilter->shouldReceive('getClassName')->once()->andReturn(m::mock('stdClass, Assetic\Filter\FilterInterface'));
162 |
163 | $vanFilter = m::mock('Basset\Filter\Filter', array(m::mock('Illuminate\Log\Writer'), 'VanFilter', array(), 'testing'))->shouldDeferMissing();
164 | $vanFilterInstance = m::mock('stdClass, Assetic\Filter\FilterInterface');
165 | $vanFilter->shouldReceive('getClassName')->once()->andReturn($vanFilterInstance);
166 |
167 | $this->asset->apply($fooFilter);
168 | $this->asset->apply($barFilter)->whenAssetIsJavascript();
169 | $this->asset->apply($bazFilter)->whenEnvironmentIs('production');
170 | $this->asset->apply($quxFilter)->whenAssetIs('.*\.js');
171 | $this->asset->apply($vanFilter)->whenAssetIs('.*\.sass');
172 |
173 | $filters = $this->asset->prepareFilters();
174 |
175 | $this->assertTrue($filters->has('FooFilter'));
176 | $this->assertTrue($filters->has('VanFilter'));
177 | $this->assertFalse($filters->has('BarFilter'));
178 | $this->assertFalse($filters->has('BazFilter'));
179 | $this->assertFalse($filters->has('QuxFilter'));
180 | }
181 |
182 |
183 | public function testAssetIsBuiltCorrectly()
184 | {
185 | $contents = 'html { background-color: #fff; }';
186 |
187 | $instantiatedFilter = m::mock('Assetic\Filter\FilterInterface');
188 | $instantiatedFilter->shouldReceive('filterLoad')->once()->andReturn(null);
189 | $instantiatedFilter->shouldReceive('filterDump')->once()->andReturnUsing(function($asset) use ($contents)
190 | {
191 | $asset->setContent(str_replace('html', 'body', $contents));
192 | });
193 |
194 | $filter = m::mock('Basset\Filter\Filter')->shouldDeferMissing();
195 | $filter->shouldReceive('setResource')->once()->with($this->asset)->andReturn(m::self());
196 | $filter->shouldReceive('getFilter')->once()->andReturn('BodyFilter');
197 | $filter->shouldReceive('getInstance')->once()->andReturn($instantiatedFilter);
198 |
199 |
200 | $config = m::mock('Illuminate\Config\Repository');
201 |
202 | $this->files->shouldReceive('getRemote')->once()->with('path/to/public/foo/bar.sass')->andReturn($contents);
203 |
204 | $this->asset->apply($filter);
205 |
206 | $this->assertEquals('body { background-color: #fff; }', $this->asset->build());
207 | }
208 |
209 |
210 | }
--------------------------------------------------------------------------------
/tests/Basset/ServerTest.php:
--------------------------------------------------------------------------------
1 | app = array(
23 | 'env' => 'testing',
24 | 'url' => new UrlGenerator(new RouteCollection, Request::create('http://localhost', 'GET')),
25 | 'config' => m::mock('Illuminate\Config\Repository'),
26 | 'basset' => m::mock('Basset\Environment'),
27 | 'basset.manifest' => new Manifest(new Filesystem, 'meta'),
28 | 'basset.builder' => m::mock('Basset\Builder\Builder'),
29 | 'basset.builder.cleaner' => m::mock('Basset\Builder\FilesystemCleaner')
30 | );
31 |
32 | $this->server = new Server($this->app);
33 | }
34 |
35 |
36 | public function testServingInvalidCollectionReturnsHtmlComment()
37 | {
38 | $this->app['basset']->shouldReceive('offsetExists')->once()->with('foo')->andReturn(false);
39 | $this->assertEquals('', $this->server->serve('foo', 'stylesheets'));
40 | }
41 |
42 |
43 | /**
44 | * @dataProvider providerServingProductionCollectionReturnsExpectedHtml
45 | */
46 | public function testServingProductionCollectionReturnsExpectedHtml($name, $group, $fingerprint, $expected)
47 | {
48 | $this->app['basset']->shouldReceive(array('offsetExists' => true, 'offsetGet' => $collection = m::mock('Basset\Collection')))->with($name);
49 |
50 | $this->app['config']->shouldReceive('get')->once()->with('basset::production')->andReturn('testing');
51 |
52 | $collection->shouldReceive('getAssetsOnlyRaw')->with($group)->andReturn(array());
53 | $collection->shouldReceive('getIdentifier')->andReturn($name);
54 |
55 | $entry = $this->app['basset.manifest']->make($collection);
56 | $entry->setProductionFingerprint($group, $fingerprint);
57 |
58 | $this->app['config']->shouldReceive('get')->with('basset::build_path')->andReturn('assets');
59 |
60 | $this->assertEquals($expected, $this->server->{$group}($name));
61 | }
62 |
63 |
64 | public function providerServingProductionCollectionReturnsExpectedHtml()
65 | {
66 | return array(
67 | array('foo', 'stylesheets', 'bar-123.css', ''),
68 | array('bar', 'javascripts', 'baz-321.js', ''),
69 | );
70 | }
71 |
72 |
73 | public function testServingDevelopmentCollectionReturnsExpectedHtml()
74 | {
75 | $this->app['basset']->shouldReceive(array('offsetExists' => true, 'offsetGet' => $collection = m::mock('Basset\Collection')))->with('foo');
76 |
77 | $this->app['config']->shouldReceive('get')->once()->with('basset::production')->andReturn('prod');
78 |
79 | $this->app['basset.builder']->shouldReceive('buildAsDevelopment')->once()->with($collection, 'stylesheets');
80 | $this->app['basset.builder.cleaner']->shouldReceive('clean')->once()->with('foo');
81 |
82 | $collection->shouldReceive('getIdentifier')->andReturn('foo');
83 | $collection->shouldReceive('getAssetsWithRaw')->once()->with('stylesheets')->andReturn($assets = array(
84 | m::mock('Basset\Asset'),
85 | m::mock('Basset\Asset'),
86 | m::mock('Basset\Asset')
87 | ));
88 |
89 | $assets[0]->shouldReceive('isRaw')->once()->andReturn(false);
90 | $assets[0]->shouldReceive('getGroup')->once()->andReturn('stylesheets');
91 | $assets[0]->shouldReceive('getRelativePath')->once()->andReturn('bar.less');
92 | $assets[1]->shouldReceive('isRaw')->once()->andReturn(true);
93 | $assets[1]->shouldReceive('getRelativePath')->once()->andReturn('qux.css');
94 | $assets[2]->shouldReceive('isRaw')->once()->andReturn(false);
95 | $assets[2]->shouldReceive('getGroup')->once()->andReturn('stylesheets');
96 | $assets[2]->shouldReceive('getRelativePath')->once()->andReturn('baz.sass');
97 |
98 | $entry = $this->app['basset.manifest']->make($collection);
99 | $entry->addDevelopmentAsset('bar.less', 'bar.css', 'stylesheets');
100 | $entry->addDevelopmentAsset('baz.sass', 'baz.css', 'stylesheets');
101 |
102 | $this->app['config']->shouldReceive('get')->with('basset::build_path')->andReturn('assets');
103 |
104 | $expected = ''.PHP_EOL.
105 | ''.PHP_EOL.
106 | '';
107 | $this->assertEquals($expected, $this->server->serve('foo', 'stylesheets'));
108 | }
109 |
110 |
111 | public function testRawAssetsAreServedBeforeBuiltCollectionHtml()
112 | {
113 | $this->app['basset']->shouldReceive(array('offsetExists' => true, 'offsetGet' => $collection = m::mock('Basset\Collection')))->with('foo');
114 |
115 | $this->app['config']->shouldReceive('get')->once()->with('basset::production')->andReturn('testing');
116 |
117 | $collection->shouldReceive('getAssetsOnlyRaw')->with('stylesheets')->andReturn(array($asset = m::mock('Basset\Asset')));
118 | $collection->shouldReceive('getIdentifier')->andReturn('foo');
119 |
120 | $asset->shouldReceive('getRelativePath')->andReturn('css/baz.css');
121 |
122 | $entry = $this->app['basset.manifest']->make($collection);
123 | $entry->setProductionFingerprint('stylesheets', 'bar-123.css');
124 |
125 | $this->app['config']->shouldReceive('get')->with('basset::build_path')->andReturn('assets');
126 |
127 | $expected = ''.PHP_EOL.
128 | '';
129 | $this->assertEquals($expected, $this->server->collection('foo.css'));
130 | }
131 |
132 |
133 | public function testServingCollectionsWithCustomFormat()
134 | {
135 | $this->app['basset']->shouldReceive(array('offsetExists' => true, 'offsetGet' => $collection = m::mock('Basset\Collection')))->with('foo');
136 |
137 | $this->app['config']->shouldReceive('get')->once()->with('basset::production')->andReturn('testing');
138 |
139 | $collection->shouldReceive('getAssetsOnlyRaw')->with('stylesheets')->andReturn(array());
140 | $collection->shouldReceive('getIdentifier')->andReturn('foo');
141 |
142 | $entry = $this->app['basset.manifest']->make($collection);
143 | $entry->setProductionFingerprint('stylesheets', 'foo-123.css');
144 |
145 | $this->app['config']->shouldReceive('get')->with('basset::build_path')->andReturn('assets');
146 |
147 | $expected = '';
148 | $this->assertEquals($expected, $this->server->stylesheets('foo', ''));
149 | }
150 |
151 |
152 | public function testServingRawAssetsOnGivenEnvironment()
153 | {
154 | $this->app['basset']->shouldReceive(array('offsetExists' => true, 'offsetGet' => $collection = m::mock('Basset\Collection')))->with('foo');
155 | $this->app['config']->shouldReceive('get')->once()->with('basset::production')->andReturn('production');
156 |
157 | $this->app['basset.builder']->shouldReceive('buildAsDevelopment')->once()->with($collection, 'stylesheets');
158 | $this->app['basset.builder.cleaner']->shouldReceive('clean')->once()->with('foo');
159 |
160 | $collection->shouldReceive('getIdentifier')->andReturn('foo');
161 | $collection->shouldReceive('getAssetsWithRaw')->once()->with('stylesheets')->andReturn($assets = array(
162 | m::mock('Basset\Asset', array(new Filesystem, m::mock('Basset\Factory\FactoryManager'), 'testing', null, null))->shouldDeferMissing(),
163 | m::mock('Basset\Asset')->shouldDeferMissing()
164 | ));
165 |
166 | $assets[0]->rawOnEnvironment('testing');
167 | $assets[0]->shouldReceive('getRelativePath')->once()->andReturn('bar.css');
168 | $assets[1]->shouldReceive('isRaw')->once()->andReturn(false);
169 | $assets[1]->shouldReceive('getGroup')->once()->andReturn('stylesheets');
170 | $assets[1]->shouldReceive('getRelativePath')->once()->andReturn('baz.sass');
171 |
172 | $entry = $this->app['basset.manifest']->make($collection);
173 | $entry->addDevelopmentAsset('baz.sass', 'baz.css', 'stylesheets');
174 |
175 | $this->app['config']->shouldReceive('get')->with('basset::build_path')->andReturn('assets');
176 |
177 | $expected = ''.PHP_EOL.
178 | '';
179 | $this->assertEquals($expected, $this->server->serve('foo', 'stylesheets'));
180 | }
181 |
182 |
183 | }
--------------------------------------------------------------------------------
/tests/Basset/Filter/FilterTest.php:
--------------------------------------------------------------------------------
1 | log = m::mock('Illuminate\Log\Writer');
17 | $this->filter = m::mock('Basset\Filter\Filter', array($this->log, 'FooFilter', array(), 'testing'))->shouldDeferMissing();
18 | $this->filter->setResource($this->resource = m::mock('Basset\Filter\Filterable'));
19 | }
20 |
21 |
22 | public function testSettingOfFilterInstantiationArguments()
23 | {
24 | $this->filter->setArguments('bar', 'baz');
25 |
26 | $arguments = $this->filter->getArguments();
27 |
28 | $this->assertEquals(array('bar', 'baz'), $arguments);
29 | }
30 |
31 |
32 | public function testSettingOfFilterInstantiationArgumentsOverwritesExistingArguments()
33 | {
34 | $this->filter->setArguments('foo', 'bar');
35 | $this->filter->setArguments('baz');
36 |
37 | $arguments = $this->filter->getArguments();
38 |
39 | $this->assertEquals(array('baz'), $arguments);
40 | }
41 |
42 |
43 | public function testSettingFilterEnvironmentRequirement()
44 | {
45 | $this->filter->whenEnvironmentIs('testing');
46 | $this->assertTrue($this->filter->processRequirements());
47 | }
48 |
49 |
50 | public function testSettingFilterStylesheetGroupRestrictionRequirement()
51 | {
52 | $this->resource->shouldReceive('isStylesheet')->once()->andReturn(false);
53 | $this->filter->whenAssetIsStylesheet();
54 | $this->assertFalse($this->filter->processRequirements());
55 | }
56 |
57 |
58 | public function testSettingFilterJavascriptGroupRestrictionRequirement()
59 | {
60 | $this->resource->shouldReceive('isJavascript')->once()->andReturn(true);
61 | $this->filter->whenAssetIsJavascript();
62 | $this->assertTrue($this->filter->processRequirements());
63 | }
64 |
65 |
66 | public function testSettingAssetNameIsRequirement()
67 | {
68 | $this->resource->shouldReceive('getRelativePath')->times(3)->andReturn('foo/bar.css');
69 |
70 | $this->filter->whenAssetIs('.*\.css');
71 | $this->assertTrue($this->filter->processRequirements());
72 |
73 | $this->filter->whenAssetIs('foo/baz.css');
74 | $this->assertFalse($this->filter->processRequirements());
75 | }
76 |
77 |
78 | public function testSettingClassExistsFilterRequirement()
79 | {
80 | $this->filter->whenClassExists('FilterTest');
81 | $this->assertTrue($this->filter->processRequirements());
82 |
83 | $this->filter->whenClassExists('FooBarBaz');
84 | $this->assertFalse($this->filter->processRequirements());
85 | }
86 |
87 |
88 | public function testSettingProductionBuildFilterRequirement()
89 | {
90 | $this->filter->whenProductionBuild();
91 | $this->assertFalse($this->filter->processRequirements());
92 |
93 | $this->filter->setProduction(true);
94 | $this->assertTrue($this->filter->processRequirements());
95 | }
96 |
97 |
98 | public function testSettingDevelopmentBuildFilterRequirement()
99 | {
100 | $this->filter->whenDevelopmentBuild();
101 | $this->assertTrue($this->filter->processRequirements());
102 |
103 | $this->filter->setProduction(true);
104 | $this->assertFalse($this->filter->processRequirements());
105 | }
106 |
107 |
108 | public function testSettingCustomFilterRequirement()
109 | {
110 | $this->resource->shouldReceive('fooBar')->times(3)->andReturn(true, true, false);
111 |
112 | $this->filter->when(function($asset)
113 | {
114 | return $asset->fooBar();
115 | });
116 | $this->assertTrue($this->filter->processRequirements());
117 |
118 | $this->filter->when(function($asset)
119 | {
120 | return $asset->fooBar();
121 | });
122 | $this->assertFalse($this->filter->processRequirements());
123 | }
124 |
125 |
126 | public function testInstantiationOfFiltersWithNoArguments()
127 | {
128 | $this->filter->shouldReceive('getClassName')->once()->andReturn('FilterStub');
129 | $instance = $this->filter->getInstance();
130 | $this->assertInstanceOf('FilterStub', $instance);
131 | }
132 |
133 |
134 | public function testInstantiationOfFiltersWithArguments()
135 | {
136 | $this->filter->shouldReceive('getClassName')->once()->andReturn('FilterWithConstructorStub');
137 | $this->filter->setArguments('bar');
138 | $instance = $this->filter->getInstance();
139 | $this->assertEquals('bar', $instance->getFooBin());
140 | }
141 |
142 |
143 | public function testInstantiationOfFiltersWithBeforeFilteringCallback()
144 | {
145 | $this->filter->shouldReceive('getClassName')->once()->andReturn('FilterStub');
146 | $this->filter->beforeFiltering(function($filter)
147 | {
148 | $filter->setFooBin('bar');
149 | });
150 | $instance = $this->filter->getInstance();
151 | $this->assertEquals('bar', $instance->getFooBin());
152 | }
153 |
154 |
155 | public function testInvalidMethodsAreHandledByResource()
156 | {
157 | $filter = new Basset\Filter\Filter($this->log, 'FooFilter', array(), 'testing');
158 | $filter->setResource($this->resource);
159 | $this->resource->shouldReceive('foo')->once()->andReturn('bar');
160 | $this->assertEquals('bar', $filter->foo());
161 | }
162 |
163 |
164 | public function testFindingOfMissingConstructorArgsWithInvalidClassReturnsCurrentInstance()
165 | {
166 | $this->filter->shouldReceive('getClassName')->once()->andReturn(null);
167 | $this->assertEquals($this->filter, $this->filter->findMissingConstructorArgs());
168 | }
169 |
170 |
171 | public function testFindingOfMissingConstructorArgsSkipsExistingArgument()
172 | {
173 | $this->filter->shouldReceive('getClassName')->once()->andReturn('FilterWithConstructorStub');
174 | $this->filter->shouldReceive('getExecutableFinder')->once()->andReturn(m::mock('Symfony\Component\Process\ExecutableFinder'));
175 | $this->filter->setArguments('foo');
176 | $this->filter->findMissingConstructorArgs();
177 | $this->assertContains('foo', $this->filter->getArguments());
178 | }
179 |
180 |
181 | public function testFindingOfMissingConstructorArgsViaEnvironmentVariable()
182 | {
183 | $this->filter->shouldReceive('getClassName')->once()->andReturn('FilterWithConstructorStub');
184 | $this->filter->shouldReceive('getExecutableFinder')->once()->andReturn(m::mock('Symfony\Component\Process\ExecutableFinder'));
185 | $this->filter->shouldReceive('getEnvironmentVariable')->once()->with('foo_bin')->andReturn('path/to/foo/bin');
186 | $this->filter->findMissingConstructorArgs();
187 | $this->assertContains('path/to/foo/bin', $this->filter->getArguments());
188 | }
189 |
190 |
191 | public function testFindingOfMissingConstructorArgsViaExecutableFinder()
192 | {
193 | $this->filter->shouldReceive('getClassName')->once()->andReturn('FilterWithConstructorStub');
194 | $this->filter->shouldReceive('getExecutableFinder')->once()->andReturn($finder = m::mock('Symfony\Component\Process\ExecutableFinder'));
195 | $finder->shouldReceive('find')->once()->with('foo')->andReturn('path/to/foo/bin');
196 | $this->filter->findMissingConstructorArgs();
197 | $this->assertContains('path/to/foo/bin', $this->filter->getArguments());
198 | }
199 |
200 |
201 | public function testFindingOfMissingConstructorArgsSetsFilterNodePaths()
202 | {
203 | $filter = m::mock('Basset\Filter\Filter', array($this->log, 'FooFilter', array('path/to/node'), 'testing'))->shouldDeferMissing();
204 | $filter->setResource($this->resource);
205 | $filter->shouldReceive('getClassName')->once()->andReturn('FilterWithConstructorStub');
206 | $filter->shouldReceive('getExecutableFinder')->once()->andReturn($finder = m::mock('Symfony\Component\Process\ExecutableFinder'));
207 | $filter->shouldReceive('getEnvironmentVariable')->once()->with('foo_bin')->andReturn('path/to/foo/bin');
208 | $filter->findMissingConstructorArgs();
209 | $this->assertContains(array('path/to/node'), $filter->getArguments());
210 | }
211 |
212 |
213 | public function testFindingOfMissingConstructorArgsIgnoresFilterWithInvalidExecutables()
214 | {
215 | $this->log->shouldReceive('error')->once();
216 | $this->filter->shouldReceive('getClassName')->once()->andReturn('FilterWithConstructorStub');
217 | $this->filter->shouldReceive('getExecutableFinder')->once()->andReturn($finder = m::mock('Symfony\Component\Process\ExecutableFinder'));
218 | $finder->shouldReceive('find')->once()->with('foo')->andReturn(false);
219 | $this->filter->findMissingConstructorArgs();
220 | $this->assertTrue($this->filter->isIgnored());
221 | }
222 |
223 |
224 | public function testFindingOfMissingConstructorArgsIsSkippedWhenNoConstructorPresent()
225 | {
226 | $this->filter->shouldReceive('getClassName')->once()->andReturn('FilterStub');
227 | $this->filter->findMissingConstructorArgs();
228 | $this->assertEmpty($this->filter->getArguments());
229 | }
230 |
231 |
232 | }
233 |
234 |
235 | class FilterStub {
236 |
237 | protected $fooBin;
238 |
239 | public function setFooBin($fooBin)
240 | {
241 | $this->fooBin = $fooBin;
242 | }
243 |
244 | public function getFooBin()
245 | {
246 | return $this->fooBin;
247 | }
248 |
249 | }
250 |
251 |
252 | class FilterWithConstructorStub {
253 |
254 | protected $fooBin;
255 |
256 | public function __construct($fooBin, $nodePaths = array())
257 | {
258 | $this->fooBin = $fooBin;
259 | }
260 |
261 | public function getFooBin()
262 | {
263 | return $this->fooBin;
264 | }
265 |
266 | }
--------------------------------------------------------------------------------
/src/config/config.php:
--------------------------------------------------------------------------------
1 | array(
30 |
31 | 'application' => function($collection)
32 | {
33 | // Switch to the stylesheets directory and require the "less" and "sass" directories.
34 | // These directories both have a filter applied to them so that the built
35 | // collection will contain valid CSS.
36 | $directory = $collection->directory('assets/stylesheets', function($collection)
37 | {
38 | $collection->requireDirectory('less')->apply('Less');
39 | $collection->requireDirectory('sass')->apply('Sass');
40 | $collection->requireDirectory();
41 | });
42 |
43 | $directory->apply('CssMin');
44 | $directory->apply('UriRewriteFilter');
45 |
46 | // Switch to the javascripts directory and require the "coffeescript" directory. As
47 | // with the above directories we'll apply the CoffeeScript filter to the directory
48 | // so the built collection contains valid JS.
49 | $directory = $collection->directory('assets/javascripts', function($collection)
50 | {
51 | $collection->requireDirectory('coffeescripts')->apply('CoffeeScript');
52 | $collection->requireDirectory();
53 | });
54 |
55 | $directory->apply('JsMin');
56 | }
57 |
58 | ),
59 |
60 | /*
61 | |--------------------------------------------------------------------------
62 | | Production Environment
63 | |--------------------------------------------------------------------------
64 | |
65 | | Basset needs to know what your production environment is so that it can
66 | | respond with the correct assets. When in production Basset will attempt
67 | | to return any built collections. If a collection has not been built
68 | | Basset will dynamically route to each asset in the collection and apply
69 | | the filters.
70 | |
71 | | The last method can be very taxing so it's highly recommended that
72 | | collections are built when deploying to a production environment.
73 | |
74 | | You can supply an array of production environment names if you need to.
75 | |
76 | */
77 |
78 | 'production' => array('production', 'prod'),
79 |
80 | /*
81 | |--------------------------------------------------------------------------
82 | | Build Path
83 | |--------------------------------------------------------------------------
84 | |
85 | | When assets are built with Artisan they will be stored within a directory
86 | | relative to the public directory.
87 | |
88 | | If the directory does not exist Basset will attempt to create it.
89 | |
90 | */
91 |
92 | 'build_path' => 'builds',
93 |
94 | /*
95 | |--------------------------------------------------------------------------
96 | | Debug
97 | |--------------------------------------------------------------------------
98 | |
99 | | Enable debugging to have potential errors or problems encountered
100 | | during operation logged to a rotating file setup.
101 | |
102 | */
103 |
104 | 'debug' => false,
105 |
106 | /*
107 | |--------------------------------------------------------------------------
108 | | Node Paths
109 | |--------------------------------------------------------------------------
110 | |
111 | | Many filters use Node to build assets. We recommend you install your
112 | | Node modules locally at the root of your application, however you can
113 | | specify additional paths to your modules.
114 | |
115 | */
116 |
117 | 'node_paths' => array(
118 |
119 | base_path().'/node_modules'
120 |
121 | ),
122 |
123 | /*
124 | |--------------------------------------------------------------------------
125 | | Gzip Built Collections
126 | |--------------------------------------------------------------------------
127 | |
128 | | To get the most speed and compression out of Basset you can enable Gzip
129 | | for every collection that is built via the command line. This is applied
130 | | to both collection builds and development builds.
131 | |
132 | | You can use the --gzip switch for on-the-fly Gzipping of collections.
133 | |
134 | */
135 |
136 | 'gzip' => false,
137 |
138 | /*
139 | |--------------------------------------------------------------------------
140 | | Asset and Filter Aliases
141 | |--------------------------------------------------------------------------
142 | |
143 | | You can define aliases for commonly used assets or filters.
144 | | An example of an asset alias:
145 | |
146 | | 'layout' => 'stylesheets/layout/master.css'
147 | |
148 | | Filter aliases are slightly different. You can define a simple alias
149 | | similar to an asset alias.
150 | |
151 | | 'YuiCss' => 'Yui\CssCompressorFilter'
152 | |
153 | | However if you want to pass in options to an aliased filter then define
154 | | the alias as a nested array. The key should be the filter and the value
155 | | should be a callback closure where you can set parameters for a filters
156 | | constructor, etc.
157 | |
158 | | 'YuiCss' => array('Yui\CssCompressorFilter', function($filter)
159 | | {
160 | | $filter->setArguments('path/to/jar');
161 | | })
162 | |
163 | |
164 | */
165 |
166 | 'aliases' => array(
167 |
168 | 'assets' => array(),
169 |
170 | 'filters' => array(
171 |
172 | /*
173 | |--------------------------------------------------------------------------
174 | | Less Filter Alias
175 | |--------------------------------------------------------------------------
176 | |
177 | | Filter is applied only when asset has a ".less" extension and it will
178 | | attempt to find missing constructor arguments.
179 | |
180 | */
181 |
182 | 'Less' => array('LessFilter', function($filter)
183 | {
184 | $filter->whenAssetIs('.*\.less')->findMissingConstructorArgs();
185 | }),
186 |
187 | /*
188 | |--------------------------------------------------------------------------
189 | | Sass Filter Alias
190 | |--------------------------------------------------------------------------
191 | |
192 | | Filter is applied only when asset has a ".sass" or ".scss" extension and
193 | | it will attempt to find missing constructor arguments.
194 | |
195 | */
196 |
197 | 'Sass' => array('Sass\ScssFilter', function($filter)
198 | {
199 | $filter->whenAssetIs('.*\.(sass|scss)')->findMissingConstructorArgs();
200 | }),
201 |
202 | /*
203 | |--------------------------------------------------------------------------
204 | | CoffeeScript Filter Alias
205 | |--------------------------------------------------------------------------
206 | |
207 | | Filter is applied only when asset has a ".coffee" extension and it will
208 | | attempt to find missing constructor arguments.
209 | |
210 | */
211 |
212 | 'CoffeeScript' => array('CoffeeScriptFilter', function($filter)
213 | {
214 | $filter->whenAssetIs('.*\.coffee')->findMissingConstructorArgs();
215 | }),
216 |
217 | /*
218 | |--------------------------------------------------------------------------
219 | | CssMin Filter Alias
220 | |--------------------------------------------------------------------------
221 | |
222 | | Filter is applied only when within the production environment and when
223 | | the "CssMin" class exists.
224 | |
225 | */
226 |
227 | 'CssMin' => array('CssMinFilter', function($filter)
228 | {
229 | $filter->whenAssetIsStylesheet()->whenProductionBuild()->whenClassExists('CssMin');
230 | }),
231 |
232 | /*
233 | |--------------------------------------------------------------------------
234 | | JsMin Filter Alias
235 | |--------------------------------------------------------------------------
236 | |
237 | | Filter is applied only when within the production environment and when
238 | | the "JsMin" class exists.
239 | |
240 | */
241 |
242 | 'JsMin' => array('JSMinFilter', function($filter)
243 | {
244 | $filter->whenAssetIsJavascript()->whenProductionBuild()->whenClassExists('JSMin');
245 | }),
246 |
247 | /*
248 | |--------------------------------------------------------------------------
249 | | UriRewrite Filter Alias
250 | |--------------------------------------------------------------------------
251 | |
252 | | Filter gets a default argument of the path to the public directory.
253 | |
254 | */
255 |
256 | 'UriRewriteFilter' => array('UriRewriteFilter', function($filter)
257 | {
258 | $filter->setArguments(public_path())->whenAssetIsStylesheet();
259 | })
260 |
261 | )
262 |
263 | )
264 |
265 | );
266 |
--------------------------------------------------------------------------------
/src/Basset/Asset.php:
--------------------------------------------------------------------------------
1 | array('css', 'sass', 'scss', 'less', 'styl', 'roo', 'gss'),
82 | 'javascripts' => array('js', 'coffee', 'dart', 'ts', 'hbs')
83 | );
84 |
85 | /**
86 | * Create a new asset instance.
87 | *
88 | * @param \Illuminate\Filesystem\Filesystem $files
89 | * @param \Basset\Factory\FactoryManager $factory
90 | * @param string $appEnvironment
91 | * @param string $absolutePath
92 | * @param string $relativePath
93 | * @return void
94 | */
95 | public function __construct(Filesystem $files, FactoryManager $factory, $appEnvironment, $absolutePath, $relativePath)
96 | {
97 | parent::__construct();
98 |
99 | $this->files = $files;
100 | $this->factory = $factory;
101 | $this->appEnvironment = $appEnvironment;
102 | $this->absolutePath = $absolutePath;
103 | $this->relativePath = $relativePath;
104 | }
105 |
106 | /**
107 | * Get the absolute path to the asset.
108 | *
109 | * @return string
110 | */
111 | public function getAbsolutePath()
112 | {
113 | return $this->absolutePath;
114 | }
115 |
116 | /**
117 | * Get the relative path to the asset.
118 | *
119 | * @return string
120 | */
121 | public function getRelativePath()
122 | {
123 | return $this->relativePath;
124 | }
125 |
126 | /**
127 | * Get the build path to the asset.
128 | *
129 | * @return string
130 | */
131 | public function getBuildPath()
132 | {
133 | $path = pathinfo($this->relativePath);
134 |
135 | $fingerprint = md5($this->filters->map(function($f) { return $f->getFilter(); })->toJson().$this->getLastModified());
136 |
137 | return "{$path['dirname']}/{$path['filename']}-{$fingerprint}.{$this->getBuildExtension()}";
138 | }
139 |
140 | /**
141 | * Get the build extension of the asset.
142 | *
143 | * @return string
144 | */
145 | public function getBuildExtension()
146 | {
147 | return $this->isJavascript() ? 'js' : 'css';
148 | }
149 |
150 | /**
151 | * Get the last modified time of the asset.
152 | *
153 | * @return int
154 | */
155 | public function getLastModified()
156 | {
157 | if ($this->lastModified)
158 | {
159 | return $this->lastModified;
160 | }
161 |
162 | return $this->lastModified = $this->isRemote() ? null : $this->files->lastModified($this->absolutePath);
163 | }
164 |
165 | /**
166 | * Determine if asset is a javascript.
167 | *
168 | * @return bool
169 | */
170 | public function isJavascript()
171 | {
172 | return $this->getGroup() == 'javascripts';
173 | }
174 |
175 | /**
176 | * Determine if asset is a stylesheet.
177 | *
178 | * @return bool
179 | */
180 | public function isStylesheet()
181 | {
182 | return $this->getGroup() == 'stylesheets';
183 | }
184 |
185 | /**
186 | * Determine if asset is remotely hosted.
187 | *
188 | * @return bool
189 | */
190 | public function isRemote()
191 | {
192 | return starts_with($this->absolutePath, '//') or (bool) filter_var($this->absolutePath, FILTER_VALIDATE_URL);
193 | }
194 |
195 | /**
196 | * Alias for \Basset\Asset::setOrder(1)
197 | *
198 | * @return Basset\Asset
199 | */
200 | public function first()
201 | {
202 | return $this->setOrder(1);
203 | }
204 |
205 | /**
206 | * Alias for \Basset\Asset::setOrder(2)
207 | *
208 | * @return \Basset\Asset
209 | */
210 | public function second()
211 | {
212 | return $this->setOrder(2);
213 | }
214 |
215 | /**
216 | * Alias for \Basset\Asset::setOrder(3)
217 | *
218 | * @return \Basset\Asset
219 | */
220 | public function third()
221 | {
222 | return $this->setOrder(3);
223 | }
224 |
225 | /**
226 | * Alias for \Basset\Asset::setOrder()
227 | *
228 | * @param int $order
229 | * @return \Basset\Asset
230 | */
231 | public function order($order)
232 | {
233 | return $this->setOrder($order);
234 | }
235 |
236 | /**
237 | * Set the order of the outputted asset.
238 | *
239 | * @param int $order
240 | * @return \Basset\Asset
241 | */
242 | public function setOrder($order)
243 | {
244 | $this->order = $order;
245 |
246 | return $this;
247 | }
248 |
249 | /**
250 | * Get the assets order.
251 | *
252 | * @return int|null
253 | */
254 | public function getOrder()
255 | {
256 | return $this->order;
257 | }
258 |
259 | /**
260 | * Set the assets group.
261 | *
262 | * @param string $group
263 | * @return \Basset\Asset
264 | */
265 | public function setGroup($group)
266 | {
267 | $this->group = $group;
268 |
269 | return $this;
270 | }
271 |
272 | /**
273 | * Get the assets group.
274 | *
275 | * @return string
276 | */
277 | public function getGroup()
278 | {
279 | if ($this->group)
280 | {
281 | return $this->group;
282 | }
283 |
284 | return $this->group = $this->detectGroupFromExtension() ?: $this->detectGroupFromContentType();
285 | }
286 |
287 | /**
288 | * Detect the group from the content type using cURL.
289 | *
290 | * @return null|string
291 | */
292 | protected function detectGroupFromContentType()
293 | {
294 | if (extension_loaded('curl'))
295 | {
296 | $this->getLogger()->warning('Attempting to determine asset group using cURL. This may have a considerable effect on application speed.');
297 |
298 | $handler = curl_init($this->absolutePath);
299 |
300 | curl_setopt($handler, CURLOPT_RETURNTRANSFER, true);
301 | curl_setopt($handler, CURLOPT_FOLLOWLOCATION, true);
302 | curl_setopt($handler, CURLOPT_HEADER, true);
303 | curl_setopt($handler, CURLOPT_NOBODY, true);
304 | curl_setopt($handler, CURLOPT_SSL_VERIFYPEER, false);
305 |
306 | curl_exec($handler);
307 |
308 | if ( ! curl_errno($handler))
309 | {
310 | $contentType = curl_getinfo($handler, CURLINFO_CONTENT_TYPE);
311 |
312 | return starts_with($contentType, 'text/css') ? 'stylesheets' : 'javascripts';
313 | }
314 | }
315 | }
316 |
317 | /**
318 | * Detect group from the assets extension.
319 | *
320 | * @return string
321 | */
322 | protected function detectGroupFromExtension()
323 | {
324 | $extension = pathinfo($this->absolutePath, PATHINFO_EXTENSION);
325 |
326 | foreach (array('stylesheets', 'javascripts') as $group)
327 | {
328 | if (in_array($extension, $this->allowedExtensions[$group]))
329 | {
330 | return $group;
331 | }
332 | }
333 | }
334 |
335 | /**
336 | * A raw asset is just excluded from the build process.
337 | *
338 | * @return \Basset\Asset
339 | */
340 | public function raw()
341 | {
342 | $this->raw = true;
343 |
344 | return $this;
345 | }
346 |
347 | /**
348 | * Sets the asset to be served raw when the application is running in a given environment.
349 | *
350 | * @param string|array $environment
351 | * @return \Basset\Asset
352 | */
353 | public function rawOnEnvironment()
354 | {
355 | $environments = array_flatten(func_get_args());
356 |
357 | if (in_array($this->appEnvironment, $environments))
358 | {
359 | return $this->raw();
360 | }
361 |
362 | return $this;
363 | }
364 |
365 | /**
366 | * Determines if the asset is to be served raw.
367 | *
368 | * @return bool
369 | */
370 | public function isRaw()
371 | {
372 | return $this->raw;
373 | }
374 |
375 | /**
376 | * Get the asset contents.
377 | *
378 | * @return string
379 | */
380 | public function getContent()
381 | {
382 | return $this->files->getRemote($this->absolutePath);
383 | }
384 |
385 | /**
386 | * Build the asset.
387 | *
388 | * @param bool $production
389 | * @return string
390 | */
391 | public function build($production = false)
392 | {
393 | $filters = $this->prepareFilters($production);
394 |
395 | $asset = new StringAsset($this->getContent(), $filters->all(), dirname($this->absolutePath), basename($this->absolutePath));
396 |
397 | return $asset->dump();
398 | }
399 |
400 | /**
401 | * Prepare the filters applied to the asset.
402 | *
403 | * @param bool $production
404 | * @return \Illuminate\Support\Collection
405 | */
406 | public function prepareFilters($production = false)
407 | {
408 | $filters = $this->filters->map(function($filter) use ($production)
409 | {
410 | $filter->setProduction($production);
411 |
412 | return $filter->getInstance();
413 | });
414 |
415 | return $filters->filter(function($filter) { return $filter instanceof FilterInterface; });
416 | }
417 |
418 | }
--------------------------------------------------------------------------------
/tests/Basset/DirectoryTest.php:
--------------------------------------------------------------------------------
1 | files = m::mock('Illuminate\Filesystem\Filesystem');
20 | $this->finder = m::mock('Basset\AssetFinder');
21 | $this->factory = m::mock('Basset\Factory\FactoryManager');
22 | $this->asset = m::mock('Basset\Factory\AssetFactory', array($this->files, $this->factory, 'testing', 'path/to/public'))->shouldDeferMissing();
23 |
24 | $this->factory->shouldReceive('get')->with('asset')->andReturn($this->asset);
25 |
26 | $this->directory = new Directory($this->factory, $this->finder, 'foo');
27 | }
28 |
29 |
30 | public function getAssetInstance($absolute = null, $relative = null)
31 | {
32 | return new Asset($this->files, $this->factory, 'testing', $absolute, $relative);
33 | }
34 |
35 |
36 | public function getLoggerMock()
37 | {
38 | return m::mock('Illuminate\Log\Writer');
39 | }
40 |
41 |
42 | public function testAddingBasicAssetFromPublicDirectory()
43 | {
44 | $asset = $this->getAssetInstance();
45 |
46 | $this->finder->shouldReceive('find')->once()->with('foo.css')->andReturn('path/to/foo.css');
47 | $this->asset->shouldReceive('make')->once()->with('path/to/foo.css')->andReturn($asset);
48 |
49 | $this->assertInstanceOf('Basset\Asset', $this->directory->stylesheet('foo.css'));
50 | $this->assertCount(1, $this->directory->getDirectoryAssets());
51 | }
52 |
53 |
54 | public function testAddingInvalidAssetReturnsBlankAssetInstance()
55 | {
56 | $asset = $this->getAssetInstance();
57 |
58 | $this->finder->shouldReceive('find')->once()->with('foo.css')->andThrow('Basset\Exceptions\AssetNotFoundException');
59 | $this->asset->shouldReceive('make')->once()->with(null)->andReturn($asset);
60 |
61 | $logger = $this->getLoggerMock()->shouldReceive('error')->once()->getMock();
62 | $this->factory->shouldReceive('getLogger')->once()->andReturn($logger);
63 |
64 | $this->assertInstanceOf('Basset\Asset', $this->directory->stylesheet('foo.css'));
65 | $this->assertCount(0, $this->directory->getDirectoryAssets());
66 | }
67 |
68 |
69 | public function testAddingAssetFiresCallback()
70 | {
71 | $asset = $this->getAssetInstance();
72 |
73 | $this->finder->shouldReceive('find')->once()->with('foo.js')->andReturn('path/to/foo.js');
74 | $this->asset->shouldReceive('make')->once()->with('path/to/foo.js')->andReturn($asset);
75 |
76 | $fired = false;
77 |
78 | $this->directory->javascript('foo.js', function() use (&$fired) { $fired = true; });
79 | $this->assertTrue($fired);
80 | }
81 |
82 |
83 | public function testChangingWorkingDirectory()
84 | {
85 | $this->finder->shouldReceive('setWorkingDirectory')->once()->with('css')->andReturn('path/to/public/css');
86 | $this->finder->shouldReceive('resetWorkingDirectory');
87 |
88 | $this->assertInstanceOf('Basset\Directory', $this->directory->directory('css'));
89 | }
90 |
91 |
92 | public function testChangingWorkingDirectoryToInvalidDirectoryReturnsBlankDirectoryInstance()
93 | {
94 | $this->finder->shouldReceive('setWorkingDirectory')->once()->with('css')->andThrow('Basset\Exceptions\DirectoryNotFoundException');
95 | $this->factory->shouldReceive('getLogger')->once()->andReturn(m::mock('Illuminate\Log\Writer')->shouldReceive('error')->once()->getMock());
96 |
97 | $this->assertInstanceOf('Basset\Directory', $this->directory->directory('css'));
98 | }
99 |
100 |
101 | public function testChangingWorkingDirectoryFiresCallback()
102 | {
103 | $this->finder->shouldReceive('setWorkingDirectory')->once()->with('css')->andReturn('path/to/public/css');
104 | $this->finder->shouldReceive('resetWorkingDirectory');
105 |
106 | $fired = false;
107 | $this->directory->directory('css', function() use (&$fired) { $fired = true; });
108 | $this->assertTrue($fired);
109 | }
110 |
111 |
112 | public function testRequireCurrentWorkingDirectory()
113 | {
114 | $directory = m::mock('Basset\Directory[iterateDirectory]', array($this->factory, $this->finder, 'foo'));
115 | $directory->shouldReceive('iterateDirectory')->once()->with('foo')->andReturn($iterator = m::mock('Iterator'));
116 |
117 | $iterator->shouldReceive('rewind')->once();
118 | $iterator->shouldReceive('valid')->times()->andReturn(true, true, false);
119 | $iterator->shouldReceive('current')->once()->andReturn($files[] = m::mock('SplFileInfo'));
120 | $iterator->shouldReceive('current')->once()->andReturn($files[] = m::mock('SplFileInfo'));
121 | $iterator->shouldReceive('next')->twice();
122 |
123 | $files[0]->shouldReceive('isFile')->andReturn(true);
124 | $files[0]->shouldReceive('getPathname')->andReturn('foo/bar.css');
125 | $files[1]->shouldReceive('isFile')->andReturn(false);
126 |
127 | $asset = $this->getAssetInstance();
128 | $this->finder->shouldReceive('find')->once()->with('foo/bar.css')->andReturn('foo/bar.css');
129 | $this->asset->shouldReceive('make')->once()->with('foo/bar.css')->andReturn($asset);
130 |
131 | $directory->requireDirectory();
132 | $this->assertCount(1, $directory->getDirectoryAssets());
133 | }
134 |
135 |
136 | public function testRequireDirectoryChangesDirectoryAndRequiresNewWorkingDirectory()
137 | {
138 | $directory = m::mock('Basset\Directory[directory]', array($this->factory, $this->finder, 'foo'));
139 |
140 | $requireDirectory = m::mock('Basset\Directory[iterateDirectory]', array($this->factory, $this->finder, 'foo/bar'));
141 | $directory->shouldReceive('directory')->with('bar')->andReturn($requireDirectory);
142 |
143 | $requireDirectory->shouldReceive('iterateDirectory')->once()->with('foo/bar')->andReturn($iterator = m::mock('Iterator'));
144 |
145 | $iterator->shouldReceive('rewind')->once();
146 | $iterator->shouldReceive('valid')->twice()->andReturn(true, false);
147 | $iterator->shouldReceive('current')->once()->andReturn($file = m::mock('SplFileInfo'));
148 | $iterator->shouldReceive('next')->once();
149 |
150 | $file->shouldReceive('isFile')->andReturn(true);
151 | $file->shouldReceive('getPathname')->andReturn('foo/bar/baz.css');
152 |
153 | $asset = $this->getAssetInstance();
154 | $this->finder->shouldReceive('find')->once()->with('foo/bar/baz.css')->andReturn('foo/bar/baz.css');
155 | $this->asset->shouldReceive('make')->once()->with('foo/bar/baz.css')->andReturn($asset);
156 |
157 | $directory->requireDirectory('bar');
158 | $this->assertCount(1, $requireDirectory->getDirectoryAssets());
159 | }
160 |
161 |
162 | public function testRequireCurrentWorkingDirectoryTree()
163 | {
164 | $directory = m::mock('Basset\Directory[recursivelyIterateDirectory]', array($this->factory, $this->finder, 'foo'));
165 | $directory->shouldReceive('recursivelyIterateDirectory')->once()->with('foo')->andReturn($iterator = m::mock('Iterator'));
166 |
167 | $iterator->shouldReceive('rewind')->once();
168 | $iterator->shouldReceive('valid')->once()->andReturn(false);
169 |
170 | $directory->requireTree();
171 | $this->assertCount(0, $directory->getDirectoryAssets());
172 | }
173 |
174 |
175 | public function testRequireTreeChangesWorkingDirectoryAndRequiresNewDirectoryTree()
176 | {
177 | $directory = m::mock('Basset\Directory[directory]', array($this->factory, $this->finder, 'foo'));
178 |
179 | $requireTree = m::mock('Basset\Directory[recursivelyIterateDirectory]', array($this->factory, $this->finder, 'foo/bar'));
180 | $directory->shouldReceive('directory')->once()->with('bar')->andReturn($requireTree);
181 |
182 | $requireTree->shouldReceive('recursivelyIterateDirectory')->once()->with('foo/bar')->andReturn($iterator = m::mock('Iterator'));
183 |
184 | $iterator->shouldReceive('rewind')->once();
185 | $iterator->shouldReceive('valid')->once()->andReturn(false);
186 |
187 | $directory->requireTree('bar');
188 | $this->assertCount(0, $directory->getDirectoryAssets());
189 | }
190 |
191 |
192 | public function testCanGetFilesystemIterator()
193 | {
194 | $this->assertInstanceOf('FilesystemIterator', $this->directory->iterateDirectory(__DIR__));
195 | }
196 |
197 |
198 | public function testCanGetRecursiveDirectoryIterator()
199 | {
200 | $this->assertInstanceOf('RecursiveIteratorIterator', $this->directory->recursivelyIterateDirectory(__DIR__));
201 | }
202 |
203 |
204 | public function testGettingIteratorsReturnsFalseForInvalidDirectories()
205 | {
206 | $this->assertFalse($this->directory->iterateDirectory('foo'));
207 | $this->assertFalse($this->directory->recursivelyIterateDirectory('foo'));
208 | }
209 |
210 |
211 | public function testGettingOfDirectoryPath()
212 | {
213 | $this->assertEquals('foo', $this->directory->getPath());
214 | }
215 |
216 |
217 | public function testExcludingOfAssetsFromDirectory()
218 | {
219 | $fooAsset = $this->getAssetInstance('path/to/foo.css', 'foo.css');
220 | $fooAsset->setOrder(1);
221 | $barAsset = $this->getAssetInstance('path/to/bar.css', 'bar.css');
222 | $barAsset->setOrder(1);
223 |
224 | $this->finder->shouldReceive('find')->once()->with('foo.css')->andReturn('path/to/foo.css');
225 | $this->asset->shouldReceive('make')->once()->with('path/to/foo.css')->andReturn($fooAsset);
226 |
227 | $this->finder->shouldReceive('find')->once()->with('bar.css')->andReturn('path/to/bar.css');
228 | $this->asset->shouldReceive('make')->once()->with('path/to/bar.css')->andReturn($barAsset);
229 |
230 | $this->directory->stylesheet('foo.css');
231 | $this->directory->stylesheet('bar.css');
232 |
233 | $this->directory->except('foo.css');
234 |
235 | $this->assertEquals($barAsset, $this->directory->getDirectoryAssets()->first());
236 | }
237 |
238 |
239 | public function testIncludingOfAssetsFromDirectory()
240 | {
241 | $fooAsset = $this->getAssetInstance('path/to/foo.css', 'foo.css');
242 | $fooAsset->setOrder(1);
243 | $barAsset = $this->getAssetInstance('path/to/bar.css', 'bar.css');
244 | $barAsset->setOrder(1);
245 |
246 | $this->finder->shouldReceive('find')->once()->with('foo.css')->andReturn('path/to/foo.css');
247 | $this->asset->shouldReceive('make')->once()->with('path/to/foo.css')->andReturn($fooAsset);
248 |
249 | $this->finder->shouldReceive('find')->once()->with('bar.css')->andReturn('path/to/bar.css');
250 | $this->asset->shouldReceive('make')->once()->with('path/to/bar.css')->andReturn($barAsset);
251 |
252 | $this->directory->stylesheet('foo.css');
253 | $this->directory->stylesheet('bar.css');
254 |
255 | $this->directory->only('foo.css');
256 |
257 | $this->assertEquals($fooAsset, $this->directory->getDirectoryAssets()->first());
258 | }
259 |
260 |
261 | public function testGetAssetsFromDirectory()
262 | {
263 | $this->assertCount(0, $this->directory->getAssets());
264 | }
265 |
266 |
267 | public function testGetAssetsFromDirectoryAndChildDirectories()
268 | {
269 | $this->finder->shouldReceive('setWorkingDirectory')->once()->with('css')->andReturn('path/to/public/css');
270 | $this->finder->shouldReceive('resetWorkingDirectory');
271 |
272 | $this->directory->directory('css');
273 |
274 | $this->assertCount(0, $this->directory->getAssets());
275 | }
276 |
277 |
278 | }
--------------------------------------------------------------------------------
/src/Basset/Directory.php:
--------------------------------------------------------------------------------
1 | factory = $factory;
64 | $this->finder = $finder;
65 | $this->path = $path;
66 | $this->assets = new \Illuminate\Support\Collection;
67 | $this->directories = new \Illuminate\Support\Collection;
68 | }
69 |
70 | /**
71 | * Find and add an asset to the directory.
72 | *
73 | * @param string $name
74 | * @param \Closure $callback
75 | * @return \Basset\Asset
76 | */
77 | public function add($name, Closure $callback = null)
78 | {
79 | try
80 | {
81 | $path = $this->finder->find($name);
82 |
83 | if (isset($this->assets[$path]))
84 | {
85 | $asset = $this->assets[$path];
86 | }
87 | else
88 | {
89 | $asset = $this->factory->get('asset')->make($path);
90 |
91 | $asset->isRemote() and $asset->raw();
92 | }
93 |
94 | is_callable($callback) and call_user_func($callback, $asset);
95 |
96 | return $this->assets[$path] = $asset;
97 | }
98 | catch (AssetNotFoundException $e)
99 | {
100 | $this->getLogger()->error(sprintf('Asset "%s" could not be found in "%s"', $name, $this->path));
101 |
102 | return $this->factory->get('asset')->make(null);
103 | }
104 | }
105 |
106 | /**
107 | * Find and add a javascript asset to the directory.
108 | *
109 | * @param string $name
110 | * @param \Closure $callback
111 | * @return \Basset\Asset
112 | */
113 | public function javascript($name, Closure $callback = null)
114 | {
115 | return $this->add($name, function($asset) use ($callback)
116 | {
117 | $asset->setGroup('javascripts');
118 |
119 | is_callable($callback) and call_user_func($callback, $asset);
120 | });
121 | }
122 |
123 | /**
124 | * Find and add a stylesheet asset to the directory.
125 | *
126 | * @param string $name
127 | * @param \Closure $callback
128 | * @return \Basset\Asset
129 | */
130 | public function stylesheet($name, Closure $callback = null)
131 | {
132 | return $this->add($name, function($asset) use ($callback)
133 | {
134 | $asset->setGroup('stylesheets');
135 |
136 | is_callable($callback) and call_user_func($callback, $asset);
137 | });
138 | }
139 |
140 | /**
141 | * Change the working directory.
142 | *
143 | * @param string $path
144 | * @param \Closure $callback
145 | * @return \Basset\Collection|\Basset\Directory
146 | */
147 | public function directory($path, Closure $callback = null)
148 | {
149 | try
150 | {
151 | $path = $this->finder->setWorkingDirectory($path);
152 |
153 | $this->directories[$path] = new Directory($this->factory, $this->finder, $path);
154 |
155 | // Once we've set the working directory we'll fire the callback so that any added assets
156 | // are relative to the working directory. After the callback we can revert the working
157 | // directory.
158 | is_callable($callback) and call_user_func($callback, $this->directories[$path]);
159 |
160 | $this->finder->resetWorkingDirectory();
161 |
162 | return $this->directories[$path];
163 | }
164 | catch (DirectoryNotFoundException $e)
165 | {
166 | $this->getLogger()->error(sprintf('Directory "%s" could not be found in "%s"', $path, $this->path));
167 |
168 | return new Directory($this->factory, $this->finder, null);
169 | }
170 | }
171 |
172 | /**
173 | * Recursively iterate through a given path.
174 | *
175 | * @param string $path
176 | * @return \RecursiveIteratorIterator
177 | */
178 | public function recursivelyIterateDirectory($path)
179 | {
180 | try
181 | {
182 | return new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path));
183 | }
184 | catch (Exception $e) { return false; }
185 | }
186 |
187 | /**
188 | * Iterate through a given path.
189 | *
190 | * @param string $path
191 | * @return \FilesystemIterator
192 | */
193 | public function iterateDirectory($path)
194 | {
195 | try
196 | {
197 | return new FilesystemIterator($path);
198 | }
199 | catch (Exception $e) { return false; }
200 | }
201 |
202 | /**
203 | * Require a directory.
204 | *
205 | * @param string $path
206 | * @return \Basset\Directory
207 | */
208 | public function requireDirectory($path = null)
209 | {
210 | if ( ! is_null($path))
211 | {
212 | return $this->directory($path)->requireDirectory();
213 | }
214 |
215 | if ($iterator = $this->iterateDirectory($this->path))
216 | {
217 | return $this->processRequire($iterator);
218 | }
219 |
220 | return $this;
221 | }
222 |
223 | /**
224 | * Require a directory tree.
225 | *
226 | * @param string $path
227 | * @return \Basset\Directory
228 | */
229 | public function requireTree($path = null)
230 | {
231 | if ( ! is_null($path))
232 | {
233 | return $this->directory($path)->requireTree();
234 | }
235 |
236 | if ($iterator = $this->recursivelyIterateDirectory($this->path))
237 | {
238 | return $this->processRequire($iterator);
239 | }
240 |
241 | return $this;
242 | }
243 |
244 | /**
245 | * Process a require of either the directory or tree.
246 | *
247 | * @param \Iterator $iterator
248 | * @return \Basset\Directory
249 | */
250 | protected function processRequire(Iterator $iterator)
251 | {
252 | // Spin through each of the files within the iterator and if their a valid asset they
253 | // are added to the array of assets for this directory.
254 | foreach ($iterator as $file)
255 | {
256 | if ( ! $file->isFile()) continue;
257 |
258 | $this->add($file->getPathname());
259 | }
260 |
261 | return $this;
262 | }
263 |
264 | /**
265 | * Exclude an array of assets.
266 | *
267 | * @param string|array $assets
268 | * @return \Basset\Directory
269 | */
270 | public function except($assets)
271 | {
272 | $assets = array_flatten(func_get_args());
273 |
274 | // Store the directory instance on a variable that we can inject into the scope of
275 | // the closure below. This allows us to call the path conversion method.
276 | $directory = $this;
277 |
278 | $this->assets = $this->assets->filter(function($asset) use ($assets, $directory)
279 | {
280 | $path = $directory->getPathRelativeToDirectory($asset->getRelativePath());
281 |
282 | return ! in_array($path, $assets);
283 | });
284 |
285 | return $this;
286 | }
287 |
288 | /**
289 | * Include only a subset of assets.
290 | *
291 | * @param string|array $assets
292 | * @return \Basset\Directory
293 | */
294 | public function only($assets)
295 | {
296 | $assets = array_flatten(func_get_args());
297 |
298 | // Store the directory instance on a variable that we can inject into the scope of
299 | // the closure below. This allows us to call the path conversion method.
300 | $directory = $this;
301 |
302 | $this->assets = $this->assets->filter(function($asset) use ($assets, $directory)
303 | {
304 | $path = $directory->getPathRelativeToDirectory($asset->getRelativePath());
305 |
306 | return in_array($path, $assets);
307 | });
308 |
309 | return $this;
310 | }
311 |
312 | /**
313 | * Get a path relative from the current directory's path.
314 | *
315 | * @param string $path
316 | * @return string
317 | */
318 | public function getPathRelativeToDirectory($path)
319 | {
320 | // Get the last segment of the directory as asset paths will be relative to this
321 | // path. We can then replace this segment with nothing in the assets path.
322 | $directoryLastSegment = substr($this->path, strrpos($this->path, '/') + 1);
323 |
324 | return trim(preg_replace('/^'.$directoryLastSegment.'/', '', $path), '/');
325 | }
326 |
327 | /**
328 | * All assets within directory will be served raw.
329 | *
330 | * @return \Basset\Directory
331 | */
332 | public function raw()
333 | {
334 | $this->assets->each(function($asset) { $asset->raw(); });
335 |
336 | return $this;
337 | }
338 |
339 | /**
340 | * All assets within directory will be served raw on a given environment.
341 | *
342 | * @return \Basset\Directory
343 | */
344 | public function rawOnEnvironment($environment)
345 | {
346 | $this->assets->each(function($asset) use ($environment) { $asset->rawOnEnvironment($environment); });
347 |
348 | return $this;
349 | }
350 |
351 | /**
352 | * Get the path to the directory.
353 | *
354 | * @return string
355 | */
356 | public function getPath()
357 | {
358 | return $this->path;
359 | }
360 |
361 | /**
362 | * Get all the assets.
363 | *
364 | * @return \Illuminate\Support\Collection
365 | */
366 | public function getAssets()
367 | {
368 | $assets = $this->assets;
369 |
370 | // Spin through each directory and recursively merge the current directories assets
371 | // on to the directories assets. This maintains the order of adding in the array
372 | // structure.
373 | $this->directories->each(function($directory) use (&$assets)
374 | {
375 | $assets = $directory->getAssets()->merge($assets);
376 | });
377 |
378 | // Spin through each of the filters and apply them to each of the assets. Every filter
379 | // is applied and then later during the build will be removed if it does not apply
380 | // to a given asset.
381 | $this->filters->each(function($filter) use (&$assets)
382 | {
383 | $assets->each(function($asset) use ($filter) { $asset->apply($filter); });
384 | });
385 |
386 | return $assets;
387 | }
388 |
389 | /**
390 | * Get the current directories assets.
391 | *
392 | * @return \Illuminate\Support\Collection
393 | */
394 | public function getDirectoryAssets()
395 | {
396 | return $this->assets;
397 | }
398 |
399 | }
--------------------------------------------------------------------------------
/tests/Basset/Builder/BuilderTest.php:
--------------------------------------------------------------------------------
1 | files = m::mock('Illuminate\Filesystem\Filesystem');
17 | $this->files->shouldReceive('exists')->once()->with('foo')->andReturn(true);
18 | $this->manifest = m::mock('Basset\Manifest\Manifest');
19 | $this->collection = m::mock('Basset\Collection');
20 | $this->builder = new Basset\Builder\Builder($this->files, $this->manifest, 'foo');
21 | }
22 |
23 |
24 | public function testBuilderChecksForBuildPathAndMakesDirectoryIfItDoesNotExist()
25 | {
26 | $this->files->shouldReceive('exists')->once()->with('foo')->andReturn(false);
27 | $this->files->shouldReceive('makeDirectory')->once()->with('foo')->andReturn(true);
28 |
29 | $builder = new Basset\Builder\Builder($this->files, $this->manifest, 'foo');
30 | }
31 |
32 |
33 | /**
34 | * @expectedException Basset\Exceptions\BuildNotRequiredException
35 | */
36 | public function testBuildingEmptyProductionCollectionThrowsBuildNotRequiredException()
37 | {
38 | $collection = m::mock('Basset\Collection');
39 | $collection->shouldReceive('getAssetsWithoutRaw')->once()->with('stylesheets')->andReturn(new Illuminate\Support\Collection);
40 | $collection->shouldReceive('getIdentifier')->once()->andReturn('foo');
41 |
42 | $this->manifest->shouldReceive('make')->once()->with('foo')->andReturn($entry = m::mock('Basset\Manifest\Entry'));
43 | $entry->shouldReceive('resetProductionFingerprint')->once()->with('stylesheets');
44 |
45 | $this->builder->buildAsProduction($collection, 'stylesheets');
46 | }
47 |
48 |
49 | /**
50 | * @expectedException Basset\Exceptions\BuildNotRequiredException
51 | */
52 | public function testBuildingExistingProductionCollectionThrowsBuildNotRequiredException()
53 | {
54 | $this->collection->shouldReceive('getAssetsWithoutRaw')->once()->with('stylesheets')->andReturn(new Illuminate\Support\Collection(array(
55 | $asset = m::mock('Basset\Asset')
56 | )));
57 | $asset->shouldReceive('build')->once()->andReturn('body { }');
58 |
59 | $this->collection->shouldReceive('getIdentifier')->once()->andReturn('foo');
60 | $this->collection->shouldReceive('getExtension')->once()->with('stylesheets')->andReturn('css');
61 |
62 | $this->manifest->shouldReceive('make')->once()->with('foo')->andReturn($entry = m::mock('Basset\Manifest\Entry'));
63 | $entry->shouldReceive('getProductionFingerprint')->with('stylesheets')->andReturn($fingerprint = 'foo-'.md5('body { }').'.css');
64 |
65 | $this->files->shouldReceive('exists')->once()->with('foo/'.$fingerprint)->andReturn(true);
66 |
67 | $this->builder->buildAsProduction($this->collection, 'stylesheets');
68 | }
69 |
70 |
71 | public function testBuildingProductionCollectionWritesToFilesystemAndSetsProductionFingerprint()
72 | {
73 | $this->collection->shouldReceive('getAssetsWithoutRaw')->once()->with('stylesheets')->andReturn(new Illuminate\Support\Collection(array(
74 | $asset = m::mock('Basset\Asset')
75 | )));
76 | $asset->shouldReceive('build')->once()->andReturn('body { }');
77 |
78 | $this->collection->shouldReceive('getIdentifier')->once()->andReturn('foo');
79 | $this->collection->shouldReceive('getExtension')->once()->with('stylesheets')->andReturn('css');
80 |
81 | $fingerprint = 'foo-'.md5('body { }').'.css';
82 |
83 | $this->manifest->shouldReceive('make')->once()->with('foo')->andReturn($entry = m::mock('Basset\Manifest\Entry'));
84 | $entry->shouldReceive('getProductionFingerprint')->with('stylesheets')->andReturn(null);
85 | $entry->shouldReceive('setProductionFingerprint')->with('stylesheets', $fingerprint);
86 |
87 | $this->files->shouldReceive('put')->once()->with('foo/'.$fingerprint, 'body { }');
88 |
89 | $this->builder->buildAsProduction($this->collection, 'stylesheets');
90 | }
91 |
92 |
93 | public function testBuildingProductionCollectionWithForce()
94 | {
95 | $this->collection->shouldReceive('getAssetsWithoutRaw')->once()->with('stylesheets')->andReturn(new Illuminate\Support\Collection(array(
96 | $asset = m::mock('Basset\Asset')
97 | )));
98 | $asset->shouldReceive('build')->once()->andReturn('body { }');
99 |
100 | $this->collection->shouldReceive('getIdentifier')->once()->andReturn('foo');
101 | $this->collection->shouldReceive('getExtension')->once()->with('stylesheets')->andReturn('css');
102 |
103 | $fingerprint = 'foo-'.md5('body { }').'.css';
104 |
105 | $this->manifest->shouldReceive('make')->once()->with('foo')->andReturn($entry = m::mock('Basset\Manifest\Entry'));
106 | $entry->shouldReceive('getProductionFingerprint')->with('stylesheets')->andReturn($fingerprint);
107 | $entry->shouldReceive('setProductionFingerprint')->with('stylesheets', $fingerprint);
108 |
109 | $this->files->shouldReceive('put')->once()->with('foo/'.$fingerprint, 'body { }');
110 |
111 | $this->builder->setForce(true);
112 | $this->builder->buildAsProduction($this->collection, 'stylesheets');
113 | }
114 |
115 |
116 | public function testBuildingProductionCollectionWithGzip()
117 | {
118 | $this->collection->shouldReceive('getAssetsWithoutRaw')->once()->with('stylesheets')->andReturn(new Illuminate\Support\Collection(array(
119 | $asset = m::mock('Basset\Asset')
120 | )));
121 | $asset->shouldReceive('build')->once()->andReturn('body { }');
122 |
123 | $this->collection->shouldReceive('getIdentifier')->once()->andReturn('foo');
124 | $this->collection->shouldReceive('getExtension')->once()->with('stylesheets')->andReturn('css');
125 |
126 | $fingerprint = 'foo-'.md5('body { }').'.css';
127 |
128 | $this->manifest->shouldReceive('make')->once()->with('foo')->andReturn($entry = m::mock('Basset\Manifest\Entry'));
129 | $entry->shouldReceive('getProductionFingerprint')->with('stylesheets')->andReturn(null);
130 | $entry->shouldReceive('setProductionFingerprint')->with('stylesheets', $fingerprint);
131 |
132 | $this->files->shouldReceive('put')->once()->with('foo/'.$fingerprint, gzencode('body { }', 9));
133 |
134 | $this->builder->setGzip(true);
135 | $this->builder->buildAsProduction($this->collection, 'stylesheets');
136 | }
137 |
138 |
139 | /**
140 | * @expectedException Basset\Exceptions\BuildNotRequiredException
141 | */
142 | public function testBuildingDevelopmentCollectionWithNoAssetsThrowsBuildNotRequiredException()
143 | {
144 | $this->collection->shouldReceive('getAssetsWithoutRaw')->once()->with('stylesheets')->andReturn(new Illuminate\Support\Collection);
145 | $this->collection->shouldReceive('getIdentifier')->once()->andReturn('foo');
146 |
147 | $this->manifest->shouldReceive('make')->once()->with('foo')->andReturn($entry = m::mock('Basset\Manifest\Entry'));
148 | $entry->shouldReceive('hasDevelopmentAssets')->once()->with('stylesheets')->andReturn(false);
149 | $entry->shouldReceive('resetDevelopmentAssets')->once()->with('stylesheets');
150 |
151 | $this->builder->buildAsDevelopment($this->collection, 'stylesheets');
152 | }
153 |
154 |
155 | /**
156 | * @expectedException Basset\Exceptions\BuildNotRequiredException
157 | */
158 | public function testBuildingDevelopmentCollectionWithAssetsThatAreAlreadyBuiltThrowsBuildNotRequiredException()
159 | {
160 | $this->collection->shouldReceive('getAssetsWithoutRaw')->once()->with('stylesheets')->andReturn(new Illuminate\Support\Collection(array(
161 | $assets[] = m::mock('Basset\Asset'),
162 | $assets[] = m::mock('Basset\Asset')
163 | )));
164 | $this->collection->shouldReceive('getIdentifier')->once()->andReturn('foo');
165 |
166 | $assets[0]->shouldReceive('getRelativePath')->once()->andReturn('bar/baz.css');
167 | $assets[0]->shouldReceive('getBuildPath')->once()->andReturn('bar/baz-123.css');
168 | $assets[1]->shouldReceive('getRelativePath')->once()->andReturn('bar/qux.css');
169 | $assets[1]->shouldReceive('getBuildPath')->once()->andReturn('bar/qux-321.css');
170 |
171 | $this->manifest->shouldReceive('make')->once()->with('foo')->andReturn($entry = m::mock('Basset\Manifest\Entry'));
172 | $entry->shouldReceive('hasDevelopmentAsset')->once()->with($assets[0])->andReturn(true);
173 | $entry->shouldReceive('getDevelopmentAsset')->once()->with($assets[0])->andReturn('bar/baz-123.css');
174 | $entry->shouldReceive('hasDevelopmentAsset')->once()->with($assets[1])->andReturn(true);
175 | $entry->shouldReceive('getDevelopmentAsset')->once()->with($assets[1])->andReturn('bar/qux-321.css');
176 |
177 | $entry->shouldReceive('hasDevelopmentAssets')->once()->with('stylesheets')->andReturn(true);
178 | $entry->shouldReceive('getDevelopmentAssets')->once()->with('stylesheets')->andReturn(array(
179 | 'bar/baz.css' => 'bar/baz-123.css',
180 | 'bar/qux.css' => 'bar/qux-321.css'
181 | ));
182 |
183 | $this->builder->buildAsDevelopment($this->collection, 'stylesheets');
184 | }
185 |
186 |
187 | public function testBuildingDevelopmentCollectionWithNoCurrentManifestEntry()
188 | {
189 | $this->collection->shouldReceive('getAssetsWithoutRaw')->once()->with('stylesheets')->andReturn(new Illuminate\Support\Collection(array(
190 | $assets[] = m::mock('Basset\Asset'),
191 | $assets[] = m::mock('Basset\Asset')
192 | )));
193 | $this->collection->shouldReceive('getIdentifier')->once()->andReturn('foo');
194 |
195 | $this->manifest->shouldReceive('make')->once()->with('foo')->andReturn($entry = m::mock('Basset\Manifest\Entry'));
196 | $entry->shouldReceive('hasDevelopmentAssets')->once()->with('stylesheets')->andReturn(false);
197 | $entry->shouldReceive('resetDevelopmentAssets')->once()->with('stylesheets');
198 |
199 | $assets[0]->shouldReceive('getBuildPath')->once()->andReturn('bar/baz-123.css');
200 | $assets[0]->shouldReceive('build')->once()->andReturn('body { }');
201 | $assets[1]->shouldReceive('getBuildPath')->once()->andReturn('bar/qux-321.css');
202 | $assets[1]->shouldReceive('build')->once()->andReturn('html { }');
203 |
204 | $entry->shouldReceive('addDevelopmentAsset')->once()->with($assets[0]);
205 | $entry->shouldReceive('addDevelopmentAsset')->once()->with($assets[1]);
206 |
207 | $this->files->shouldReceive('exists')->once()->with('foo/foo/bar')->andReturn(false);
208 | $this->files->shouldReceive('exists')->once()->with('foo/foo/bar')->andReturn(true);
209 | $this->files->shouldReceive('makeDirectory')->once()->with('foo/foo/bar', 0777, true);
210 |
211 | $this->files->shouldReceive('put')->once()->with('foo/foo/bar/baz-123.css', 'body { }');
212 | $this->files->shouldReceive('put')->once()->with('foo/foo/bar/qux-321.css', 'html { }');
213 |
214 | $this->builder->buildAsDevelopment($this->collection, 'stylesheets');
215 | }
216 |
217 |
218 | public function testBuildingDevelopmentCollectionWithNoChangesButWithForcing()
219 | {
220 | $this->collection->shouldReceive('getAssetsWithoutRaw')->once()->with('stylesheets')->andReturn(new Illuminate\Support\Collection(array(
221 | $assets[] = m::mock('Basset\Asset'),
222 | $assets[] = m::mock('Basset\Asset')
223 | )));
224 | $this->collection->shouldReceive('getIdentifier')->once()->andReturn('foo');
225 |
226 | $assets[0]->shouldReceive('getRelativePath')->once()->andReturn('bar/baz.css');
227 | $assets[0]->shouldReceive('getBuildPath')->once()->andReturn('bar/baz-123.css');
228 | $assets[0]->shouldReceive('build')->once()->andReturn('body { }');
229 | $assets[1]->shouldReceive('getRelativePath')->once()->andReturn('bar/qux.css');
230 | $assets[1]->shouldReceive('getBuildPath')->once()->andReturn('bar/qux-321.css');
231 | $assets[1]->shouldReceive('build')->once()->andReturn('html { }');
232 |
233 | $this->manifest->shouldReceive('make')->once()->with('foo')->andReturn($entry = m::mock('Basset\Manifest\Entry'));
234 | $entry->shouldReceive('hasDevelopmentAssets')->once()->with('stylesheets')->andReturn(true);
235 | $entry->shouldReceive('resetDevelopmentAssets')->once()->with('stylesheets');
236 | $entry->shouldReceive('getDevelopmentAssets')->once()->with('stylesheets')->andReturn(array(
237 | 'bar/baz.css' => 'bar/baz-123.css',
238 | 'bar/qux.css' => 'bar/qux-321.css'
239 | ));
240 |
241 | $entry->shouldReceive('addDevelopmentAsset')->once()->with($assets[0]);
242 | $entry->shouldReceive('addDevelopmentAsset')->once()->with($assets[1]);
243 |
244 | $this->files->shouldReceive('exists')->once()->with('foo/foo/bar')->andReturn(false);
245 | $this->files->shouldReceive('exists')->once()->with('foo/foo/bar')->andReturn(true);
246 | $this->files->shouldReceive('makeDirectory')->once()->with('foo/foo/bar', 0777, true);
247 |
248 | $this->files->shouldReceive('put')->once()->with('foo/foo/bar/baz-123.css', 'body { }');
249 | $this->files->shouldReceive('put')->once()->with('foo/foo/bar/qux-321.css', 'html { }');
250 |
251 | $this->builder->setForce(true);
252 | $this->builder->buildAsDevelopment($this->collection, 'stylesheets');
253 | }
254 |
255 |
256 | public function testBuildingDevelopmentCollectionWithGzip()
257 | {
258 | $this->collection->shouldReceive('getAssetsWithoutRaw')->once()->with('stylesheets')->andReturn(new Illuminate\Support\Collection(array(
259 | $asset = m::mock('Basset\Asset')
260 | )));
261 | $this->collection->shouldReceive('getIdentifier')->once()->andReturn('foo');
262 |
263 | $asset->shouldReceive('getRelativePath')->once()->andReturn('bar/baz.css');
264 | $asset->shouldReceive('getBuildPath')->once()->andReturn('bar/baz-123.css');
265 | $asset->shouldReceive('build')->once()->andReturn('body { }');
266 |
267 | $this->manifest->shouldReceive('make')->once()->with('foo')->andReturn($entry = m::mock('Basset\Manifest\Entry'));
268 | $entry->shouldReceive('hasDevelopmentAssets')->once()->with('stylesheets')->andReturn(true);
269 | $entry->shouldReceive('getDevelopmentAssets')->once()->with('stylesheets')->andReturn(array('bar/baz.css' => 'bar/baz-123.css'));
270 |
271 | $entry->shouldReceive('hasDevelopmentAsset')->once()->with($asset)->andReturn(false);
272 | $entry->shouldReceive('addDevelopmentAsset')->once()->with($asset);
273 |
274 | $this->files->shouldReceive('exists')->once()->with('foo/foo/bar')->andReturn(true);
275 |
276 | $this->files->shouldReceive('put')->once()->with('foo/foo/bar/baz-123.css', gzencode('body { }', 9));
277 |
278 | $this->builder->setGzip(true);
279 | $this->builder->buildAsDevelopment($this->collection, 'stylesheets');
280 | }
281 |
282 |
283 | }
--------------------------------------------------------------------------------