parent
ff016ad523
commit
14a700d00a
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,2 @@ |
||||
composer.lock |
||||
vendor |
||||
@ -0,0 +1,4 @@ |
||||
build/ |
||||
logs/ |
||||
reports/ |
||||
dist/ |
||||
@ -0,0 +1,3 @@ |
||||
[submodule "lib/vendor/doctrine-build-common"] |
||||
path = lib/vendor/doctrine-build-common |
||||
url = git://github.com/doctrine/doctrine-build-common.git |
||||
@ -0,0 +1,3 @@ |
||||
Doctrine/Tests/Proxies/ |
||||
Doctrine/Tests/ORM/Proxy/generated/ |
||||
Doctrine/Tests/ORM/Tools/Export/export |
||||
@ -0,0 +1,6 @@ |
||||
build/ |
||||
logs/ |
||||
reports/ |
||||
dist/ |
||||
download/ |
||||
lib/Doctrine/Common/ |
||||
@ -0,0 +1,9 @@ |
||||
[submodule "lib/vendor/doctrine-common"] |
||||
path = lib/vendor/doctrine-common |
||||
url = git://github.com/doctrine/common.git |
||||
[submodule "lib/vendor/Symfony/Component/Console"] |
||||
path = lib/vendor/Symfony/Component/Console |
||||
url = git://github.com/symfony/Console.git |
||||
[submodule "lib/vendor/doctrine-build-common"] |
||||
path = lib/vendor/doctrine-build-common |
||||
url = git://github.com/doctrine/doctrine-build-common.git |
||||
@ -0,0 +1,3 @@ |
||||
Doctrine/Tests/Proxies/ |
||||
Doctrine/Tests/ORM/Proxy/generated/ |
||||
Doctrine/Tests/ORM/Tools/Export/export |
||||
@ -0,0 +1,8 @@ |
||||
build.properties |
||||
build/ |
||||
logs/ |
||||
reports/ |
||||
dist/ |
||||
tests/phpunit.xml |
||||
vendor/ |
||||
composer.lock |
||||
@ -0,0 +1,11 @@ |
||||
build/ |
||||
logs/ |
||||
reports/ |
||||
dist/ |
||||
download/ |
||||
lib/api/ |
||||
lib/Doctrine/Common |
||||
lib/Doctrine/DBAL |
||||
/.settings/ |
||||
.buildpath |
||||
.project |
||||
@ -0,0 +1,15 @@ |
||||
[submodule "lib/vendor/doctrine-common"] |
||||
path = lib/vendor/doctrine-common |
||||
url = git://github.com/doctrine/common.git |
||||
[submodule "lib/vendor/doctrine-dbal"] |
||||
path = lib/vendor/doctrine-dbal |
||||
url = git://github.com/doctrine/dbal.git |
||||
[submodule "lib/vendor/Symfony/Component/Console"] |
||||
path = lib/vendor/Symfony/Component/Console |
||||
url = git://github.com/symfony/Console.git |
||||
[submodule "lib/vendor/Symfony/Component/Yaml"] |
||||
path = lib/vendor/Symfony/Component/Yaml |
||||
url = git://github.com/symfony/Yaml.git |
||||
[submodule "lib/vendor/doctrine-build-common"] |
||||
path = lib/vendor/doctrine-build-common |
||||
url = git://github.com/doctrine/doctrine-build-common.git |
||||
@ -0,0 +1,3 @@ |
||||
Doctrine/Tests/Proxies/ |
||||
Doctrine/Tests/ORM/Proxy/generated/ |
||||
Doctrine/Tests/ORM/Tools/Export/export |
||||
@ -0,0 +1 @@ |
||||
configdoc/usage.xml -crlf |
||||
@ -0,0 +1,24 @@ |
||||
tags |
||||
conf/ |
||||
test-settings.php |
||||
config-schema.php |
||||
library/HTMLPurifier/DefinitionCache/Serializer/*/ |
||||
library/standalone/ |
||||
library/HTMLPurifier.standalone.php |
||||
library/HTMLPurifier*.tgz |
||||
library/package*.xml |
||||
smoketests/test-schema.html |
||||
configdoc/*.html |
||||
configdoc/configdoc.xml |
||||
docs/doxygen* |
||||
*.phpt.diff |
||||
*.phpt.exp |
||||
*.phpt.log |
||||
*.phpt.out |
||||
*.phpt.php |
||||
*.phpt.skip.php |
||||
*.htmlt.ini |
||||
*.patch |
||||
/*.php |
||||
vendor |
||||
composer.lock |
||||
@ -0,0 +1,2 @@ |
||||
migrate.php |
||||
htmlpurifier/* |
||||
@ -0,0 +1,8 @@ |
||||
tests/phpunit.xml |
||||
tests/temp/*.php |
||||
scripts/ |
||||
vendor/ |
||||
tags |
||||
.vimrc.local |
||||
/composer.lock |
||||
/composer.phar |
||||
@ -0,0 +1,2 @@ |
||||
composer.lock |
||||
vendor |
||||
@ -0,0 +1,4 @@ |
||||
/phpunit.xml |
||||
/vendor/ |
||||
/composer.lock |
||||
/composer.phar |
||||
@ -0,0 +1,2 @@ |
||||
phpunit.xml |
||||
vendor/ |
||||
@ -0,0 +1,13 @@ |
||||
language: php |
||||
|
||||
php: |
||||
- 5.3 |
||||
- 5.4 |
||||
|
||||
before_script: |
||||
- git clone https://github.com/kamicane/packager.git vendor/packager --quiet --depth 1 |
||||
- git clone https://github.com/leafo/lessphp.git vendor/lessphp --quiet --depth 1 |
||||
- git clone https://github.com/fabpot/Twig.git vendor/twig --quiet --depth 1 |
||||
- svn checkout http://cssmin.googlecode.com/svn/trunk/ vendor/cssmin --quiet |
||||
|
||||
script: phpunit --configuration phpunit.travis.xml |
||||
@ -0,0 +1,36 @@ |
||||
1.0.4 (August 28, 2012) |
||||
----------------------- |
||||
|
||||
* Fixed the Twig tag to avoid a fatal error when left unclosed |
||||
* Added the HashableInterface for non-serialiable filters |
||||
* Fixed a bug for compass on windows |
||||
|
||||
1.0.3 (March 2, 2012) |
||||
--------------------- |
||||
|
||||
* Added "boring" option to Compass filter |
||||
* Fixed accumulation of load paths in Compass filter |
||||
* Fixed issues in CssImport and CssRewrite filters |
||||
|
||||
1.0.2 (August 26, 2011) |
||||
----------------------- |
||||
|
||||
* Twig 1.2 compatibility |
||||
* Fixed filtering of large LessCSS assets |
||||
* Fixed escaping of commands on Windows |
||||
* Misc fixes to Compass filter |
||||
* Removed default CssEmbed charset |
||||
|
||||
1.0.1 (July 15, 2011) |
||||
--------------------- |
||||
|
||||
* Fixed Twig error handling |
||||
* Removed use of STDIN |
||||
* Added inheritance of environment variables |
||||
* Fixed Compass on Windows |
||||
* Improved escaping of commands |
||||
|
||||
1.0.0 (July 10, 2011) |
||||
--------------------- |
||||
|
||||
* Initial release |
||||
@ -0,0 +1,19 @@ |
||||
Copyright (c) 2010-2012 OpenSky Project Inc |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is furnished |
||||
to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||
THE SOFTWARE. |
||||
@ -0,0 +1,283 @@ |
||||
# Assetic  # |
||||
|
||||
Assetic is an asset management framework for PHP. |
||||
|
||||
``` php |
||||
<?php |
||||
|
||||
use Assetic\Asset\AssetCollection; |
||||
use Assetic\Asset\FileAsset; |
||||
use Assetic\Asset\GlobAsset; |
||||
|
||||
$js = new AssetCollection(array( |
||||
new GlobAsset('/path/to/js/*'), |
||||
new FileAsset('/path/to/another.js'), |
||||
)); |
||||
|
||||
// the code is merged when the asset is dumped |
||||
echo $js->dump(); |
||||
``` |
||||
|
||||
Assets |
||||
------ |
||||
|
||||
An Assetic asset is something with filterable content that can be loaded and |
||||
dumped. An asset also includes metadata, some of which can be manipulated and |
||||
some of which is immutable. |
||||
|
||||
| **Property** | **Accessor** | **Mutator** | |
||||
|--------------|-----------------|---------------| |
||||
| content | getContent | setContent | |
||||
| mtime | getLastModified | n/a | |
||||
| source root | getSourceRoot | n/a | |
||||
| source path | getSourcePath | n/a | |
||||
| target path | getTargetPath | setTargetPath | |
||||
|
||||
Filters |
||||
------- |
||||
|
||||
Filters can be applied to manipulate assets. |
||||
|
||||
``` php |
||||
<?php |
||||
|
||||
use Assetic\Asset\AssetCollection; |
||||
use Assetic\Asset\FileAsset; |
||||
use Assetic\Asset\GlobAsset; |
||||
use Assetic\Filter\LessFilter; |
||||
use Assetic\Filter\Yui; |
||||
|
||||
$css = new AssetCollection(array( |
||||
new FileAsset('/path/to/src/styles.less', array(new LessFilter())), |
||||
new GlobAsset('/path/to/css/*'), |
||||
), array( |
||||
new Yui\CssCompressorFilter('/path/to/yuicompressor.jar'), |
||||
)); |
||||
|
||||
// this will echo CSS compiled by LESS and compressed by YUI |
||||
echo $css->dump(); |
||||
``` |
||||
|
||||
The filters applied to the collection will cascade to each asset leaf if you |
||||
iterate over it. |
||||
|
||||
``` php |
||||
<?php |
||||
|
||||
foreach ($css as $leaf) { |
||||
// each leaf is compressed by YUI |
||||
echo $leaf->dump(); |
||||
} |
||||
``` |
||||
|
||||
The core provides the following filters in the `Assetic\Filter` namespace: |
||||
|
||||
* `CoffeeScriptFilter`: compiles CoffeeScript into Javascript |
||||
* `CssEmbedFilter`: embeds image data in your stylesheets |
||||
* `CssImportFilter`: inlines imported stylesheets |
||||
* `CssMinFilter`: minifies CSS |
||||
* `CssRewriteFilter`: fixes relative URLs in CSS assets when moving to a new URL |
||||
* `GoogleClosure\CompilerApiFilter`: compiles Javascript using the Google Closure Compiler API |
||||
* `GoogleClosure\CompilerJarFilter`: compiles Javascript using the Google Closure Compiler JAR |
||||
* `JpegoptimFilter`: optimize your JPEGs |
||||
* `JpegtranFilter`: optimize your JPEGs |
||||
* `LessFilter`: parses LESS into CSS (using less.js with node.js) |
||||
* `LessphpFilter`: parses LESS into CSS (using lessphp) |
||||
* `OptiPngFilter`: optimize your PNGs |
||||
* `PngoutFilter`: optimize your PNGs |
||||
* `CompassFilter`: Compass CSS authoring framework |
||||
* `Sass\SassFilter`: parses SASS into CSS |
||||
* `Sass\ScssFilter`: parses SCSS into CSS |
||||
* `SprocketsFilter`: Sprockets Javascript dependency management |
||||
* `StylusFilter`: parses STYL into CSS |
||||
* `Yui\CssCompressorFilter`: compresses CSS using the YUI compressor |
||||
* `Yui\JsCompressorFilter`: compresses Javascript using the YUI compressor |
||||
|
||||
Asset Manager |
||||
------------- |
||||
|
||||
An asset manager is provided for organizing assets. |
||||
|
||||
``` php |
||||
<?php |
||||
|
||||
use Assetic\AssetManager; |
||||
use Assetic\Asset\FileAsset; |
||||
use Assetic\Asset\GlobAsset; |
||||
|
||||
$am = new AssetManager(); |
||||
$am->set('jquery', new FileAsset('/path/to/jquery.js')); |
||||
$am->set('base_css', new GlobAsset('/path/to/css/*')); |
||||
``` |
||||
|
||||
The asset manager can also be used to reference assets to avoid duplication. |
||||
|
||||
``` php |
||||
<?php |
||||
|
||||
use Assetic\Asset\AssetCollection; |
||||
use Assetic\Asset\AssetReference; |
||||
use Assetic\Asset\FileAsset; |
||||
|
||||
$am->set('my_plugin', new AssetCollection(array( |
||||
new AssetReference($am, 'jquery'), |
||||
new FileAsset('/path/to/jquery.plugin.js'), |
||||
))); |
||||
``` |
||||
|
||||
Filter Manager |
||||
-------------- |
||||
|
||||
A filter manager is also provided for organizing filters. |
||||
|
||||
``` php |
||||
<?php |
||||
|
||||
use Assetic\FilterManager; |
||||
use Assetic\Filter\Sass\SassFilter; |
||||
use Assetic\Filter\Yui; |
||||
|
||||
$fm = new FilterManager(); |
||||
$fm->set('sass', new SassFilter('/path/to/parser/sass')); |
||||
$fm->set('yui_css', new Yui\CssCompressorFilter('/path/to/yuicompressor.jar')); |
||||
``` |
||||
|
||||
Asset Factory |
||||
------------- |
||||
|
||||
If you'd rather not create all these objects by hand, you can use the asset |
||||
factory, which will do most of the work for you. |
||||
|
||||
``` php |
||||
<?php |
||||
|
||||
use Assetic\Factory\AssetFactory; |
||||
|
||||
$factory = new AssetFactory('/path/to/asset/directory/'); |
||||
$factory->setAssetManager($am); |
||||
$factory->setFilterManager($fm); |
||||
$factory->setDebug(true); |
||||
|
||||
$css = $factory->createAsset(array( |
||||
'@reset', // load the asset manager's "reset" asset |
||||
'css/src/*.scss', // load every scss files from "/path/to/asset/directory/css/src/" |
||||
), array( |
||||
'scss', // filter through the filter manager's "scss" filter |
||||
'?yui_css', // don't use this filter in debug mode |
||||
)); |
||||
|
||||
echo $css->dump(); |
||||
``` |
||||
|
||||
Prefixing a filter name with a question mark, as `yui_css` is here, will cause |
||||
that filter to be omitted when the factory is in debug mode. |
||||
|
||||
Caching |
||||
------- |
||||
|
||||
A simple caching mechanism is provided to avoid unnecessary work. |
||||
|
||||
``` php |
||||
<?php |
||||
|
||||
use Assetic\Asset\AssetCache; |
||||
use Assetic\Asset\FileAsset; |
||||
use Assetic\Cache\FilesystemCache; |
||||
use Assetic\Filter\Yui; |
||||
|
||||
$yui = new Yui\JsCompressorFilter('/path/to/yuicompressor.jar'); |
||||
$js = new AssetCache( |
||||
new FileAsset('/path/to/some.js', array($yui)), |
||||
new FilesystemCache('/path/to/cache') |
||||
); |
||||
|
||||
// the YUI compressor will only run on the first call |
||||
$js->dump(); |
||||
$js->dump(); |
||||
$js->dump(); |
||||
``` |
||||
|
||||
Static Assets |
||||
------------- |
||||
|
||||
Alternatively you can just write filtered assets to your web directory and be |
||||
done with it. |
||||
|
||||
``` php |
||||
<?php |
||||
|
||||
use Assetic\AssetWriter; |
||||
|
||||
$writer = new AssetWriter('/path/to/web'); |
||||
$writer->writeManagerAssets($am); |
||||
``` |
||||
|
||||
Twig |
||||
---- |
||||
|
||||
To use the Assetic [Twig][3] extension you must register it to your Twig |
||||
environment: |
||||
|
||||
``` php |
||||
<?php |
||||
|
||||
$twig->addExtension(new AsseticExtension($factory, $debug)); |
||||
``` |
||||
|
||||
Once in place, the extension exposes a stylesheets and a javascripts tag with a syntax similar |
||||
to what the asset factory uses: |
||||
|
||||
``` html+jinja |
||||
{% stylesheets '/path/to/sass/main.sass' filter='sass,?yui_css' output='css/all.css' %} |
||||
<link href="{{ asset_url }}" type="text/css" rel="stylesheet" /> |
||||
{% endstylesheets %} |
||||
``` |
||||
|
||||
This example will render one `link` element on the page that includes a URL |
||||
where the filtered asset can be found. |
||||
|
||||
When the extension is in debug mode, this same tag will render multiple `link` |
||||
elements, one for each asset referenced by the `css/src/*.sass` glob. The |
||||
specified filters will still be applied, unless they are marked as optional |
||||
using the `?` prefix. |
||||
|
||||
This behavior can also be triggered by setting a `debug` attribute on the tag: |
||||
|
||||
``` html+jinja |
||||
{% stylesheets 'css/*' debug=true %} ... {% stylesheets %} |
||||
``` |
||||
|
||||
These assets need to be written to the web directory so these URLs don't |
||||
return 404 errors. |
||||
|
||||
``` php |
||||
<?php |
||||
|
||||
use Assetic\AssetWriter; |
||||
use Assetic\Extension\Twig\TwigFormulaLoader; |
||||
use Assetic\Extension\Twig\TwigResource; |
||||
use Assetic\Factory\LazyAssetManager; |
||||
|
||||
$am = new LazyAssetManager($factory); |
||||
|
||||
// enable loading assets from twig templates |
||||
$am->setLoader('twig', new TwigFormulaLoader($twig)); |
||||
|
||||
// loop through all your templates |
||||
foreach ($templates as $template) { |
||||
$resource = new TwigResource($twigLoader, $template); |
||||
$am->addResource($resource, 'twig'); |
||||
} |
||||
|
||||
$writer = new AssetWriter('/path/to/web'); |
||||
$writer->writeManagerAssets($am); |
||||
``` |
||||
|
||||
--- |
||||
|
||||
Assetic is based on the Python [webassets][1] library (available on |
||||
[GitHub][2]). |
||||
|
||||
[1]: http://elsdoerfer.name/docs/webassets |
||||
[2]: https://github.com/miracle2k/webassets |
||||
[3]: http://twig.sensiolabs.org |
||||
@ -0,0 +1,21 @@ |
||||
{ |
||||
"name": "kriswallsmith/assetic", |
||||
"description": "Asset Management for PHP", |
||||
"keywords": ["assets", "compression", "minification"], |
||||
"homepage": "https://github.com/kriswallsmith/assetic", |
||||
"type": "library", |
||||
"license": "MIT", |
||||
"authors": [ |
||||
{ |
||||
"name": "Kris Wallsmith", |
||||
"email": "kris.wallsmith@gmail.com", |
||||
"homepage": "http://kriswallsmith.net/" |
||||
} |
||||
], |
||||
"require": { |
||||
"php": ">=5.3.1" |
||||
}, |
||||
"autoload": { |
||||
"psr-0": { "Assetic": "src/" } |
||||
} |
||||
} |
||||
@ -0,0 +1,32 @@ |
||||
Building and Dumping Assets |
||||
--------------------------- |
||||
|
||||
The is the simplest approach to using Assetic. It involves two steps: |
||||
|
||||
1. Create a PHP script in your web directory that uses the Assetic OOP API to |
||||
create and output an asset. |
||||
2. Reference that file from your template. |
||||
|
||||
For example, you could create a file in your web directory at |
||||
`assets/javascripts.php` with the following code: |
||||
|
||||
use Assetic\Asset\AssetCollection; |
||||
use Assetic\Asset\FileAsset; |
||||
use Assetic\Filter\Yui\JsCompressorFilter as YuiCompressorFilter; |
||||
|
||||
$js = new AssetCollection(array( |
||||
new FileAsset(__DIR__.'/jquery.js'), |
||||
new FileAsset(__DIR__.'/application.js'), |
||||
), array( |
||||
new YuiCompressorFilter('/path/to/yuicompressor.jar'), |
||||
)); |
||||
|
||||
header('Content-Type: application/js'); |
||||
echo $js->dump(); |
||||
|
||||
In your HTML template you would include this generated Javascript using a |
||||
simple `<script>` tag: |
||||
|
||||
<script src="/assets/javascripts.php"></script> |
||||
|
||||
Next: [Basic Concepts](concepts.md) |
||||
@ -0,0 +1,129 @@ |
||||
In order to use the Assetic OOP API you must first understand the two central |
||||
concepts of Assetic: assets and filters. |
||||
|
||||
### What is an Asset? |
||||
|
||||
As asset is an object that has content and metadata which can be loaded and |
||||
dumped. Your assets will probably fall into three categories: Javascripts, |
||||
stylesheets and images. Most assets will be loaded from files in your |
||||
filesystem, but they can also be loaded via HTTP, a database, from a string, |
||||
or virtually anything else. All that an asset has to do is fulfill Assetic's |
||||
basic asset interface. |
||||
|
||||
### What is a Filter? |
||||
|
||||
A filter is an object that acts upon an asset's content when that asset is |
||||
loaded and/or dumped. Similar to assets, a filter can do virtually anything, |
||||
as long as it implements Assetic's filter interface. |
||||
|
||||
Here is a list of some of the tools that can be applied to assets using a |
||||
filter: |
||||
|
||||
* CoffeeScript |
||||
* CssEmbed |
||||
* CssMin |
||||
* Google Closure Compiler |
||||
* jpegoptim |
||||
* jpegtran |
||||
* Less |
||||
* LessPHP |
||||
* optipng |
||||
* Packager |
||||
* pngout |
||||
* SASS |
||||
* Sprockets (version 1) |
||||
* Stylus |
||||
* YUI Compressor |
||||
|
||||
### Using Assets and Filters |
||||
|
||||
You need to start by creating an asset object. This will probably mean |
||||
instantiating a `FileAsset` instance, which takes a filesystem path as its |
||||
first argument: |
||||
|
||||
$asset = new Assetic\Asset\FileAsset('/path/to/main.css'); |
||||
|
||||
Once you have an asset you can begin adding filters to it by calling |
||||
`ensureFilter()`. For example, you can add a filter that applies the YUI |
||||
Compressor to the contents of the asset: |
||||
|
||||
$yui = new Assetic\Filter\Yui\CssCompressorFilter('/path/to/yui.jar'); |
||||
$asset->ensureFilter($yui); |
||||
|
||||
Once you've added as many filters as you'd like you can output the finished |
||||
asset to the browser: |
||||
|
||||
header('Content-Type: text/css'); |
||||
echo $asset->dump(); |
||||
|
||||
### Asset Collections |
||||
|
||||
It is a good idea to combine assets of the same type into a single file to |
||||
avoid unnecessary HTTP requests. You can do this in Assetic using the |
||||
`AssetCollection` class. This class is just like any other asset in Assetic's |
||||
eyes as it implements the asset interface, but under the hood it allows you to |
||||
combine multiple assets into one. |
||||
|
||||
use Assetic\Asset\AssetCollection; |
||||
|
||||
$asset = new AssetCollection(array( |
||||
new FileAsset('/path/to/js/jquery.js'), |
||||
new FileAsset('/path/to/js/jquery.plugin.js'), |
||||
new FileAsset('/path/to/js/application.js'), |
||||
)); |
||||
|
||||
### Nested Asset Collections |
||||
|
||||
The collection class implements the asset interface and all assets passed into |
||||
a collection must implement the same interface, which means you can easily |
||||
nest collections within one another: |
||||
|
||||
use Assetic\Asset\AssetCollection; |
||||
use Assetic\Asset\GlobAsset; |
||||
use Assetic\Asset\HttpAsset; |
||||
|
||||
$asset = new AssetCollection(array( |
||||
new HttpAsset('http://example.com/jquery.min.js'), |
||||
new GlobAsset('/path/to/js/*'), |
||||
)); |
||||
|
||||
The `HttpAsset` class is a special asset class that loads a file over HTTP; |
||||
`GlobAsset` is a special asset collection class that loads files based on a |
||||
filesystem glob -- both implement the asset interface. |
||||
|
||||
This concept of nesting asset collection become even more powerful when you |
||||
start applying different sets of filters to each collection. Imagine some of |
||||
your application's stylesheets are written in SASS, while some are written in |
||||
vanilla CSS. You can combine all of these into one seamless CSS asset: |
||||
|
||||
use Assetic\Asset\AssetCollection; |
||||
use Assetic\Asset\GlobAsset; |
||||
use Assetic\Filter\SassFilter; |
||||
use Assetic\Filter\Yui\CssCompressorFilter; |
||||
|
||||
$css = new AssetCollection(array( |
||||
new GlobAsset('/path/to/sass/*.sass', array(new SassFilter())), |
||||
new GlobAsset('/path/to/css/*.css'), |
||||
), array( |
||||
new YuiCompressorFilter('/path/to/yuicompressor.jar'), |
||||
)); |
||||
|
||||
You'll notice I've also applied the YUI compressor filter to the combined |
||||
asset so all CSS will be minified. |
||||
|
||||
### Iterating over an Asset Collection |
||||
|
||||
Once you have an asset collection you can iterate over it like you would a |
||||
plain old PHP array: |
||||
|
||||
echo "Source paths:\n"; |
||||
foreach ($collection as $asset) { |
||||
echo ' - '.$asset->getSourcePath()."\n"; |
||||
} |
||||
|
||||
The asset collection iterates recursively, which means you will only see the |
||||
"leaf" assets during iteration. Iteration also includes a smart filter which |
||||
ensures you only see each asset once, even if the same asset has been included |
||||
multiple times. |
||||
|
||||
Next: [Defining Assets "On The Fly"](define.md) |
||||
@ -0,0 +1,145 @@ |
||||
Defining Assets "On The Fly" |
||||
---------------------------- |
||||
|
||||
The second approach to using Assetic involves defining your application's |
||||
assets "on the fly" in your templates, instead of in an isolated PHP file. |
||||
Using this approach, your PHP template would look something like this: |
||||
|
||||
<script src="<?php echo assetic_javascripts('js/*', 'yui_js') ?>"></script> |
||||
|
||||
This call to `assetic_javascripts()` serves a dual purpose. It will be read by |
||||
the Assetic "formula loader" which will extract an asset "formula" that can be |
||||
used to build, dump and output the asset. It will also be executed when the |
||||
template is rendered, at which time the path to the output asset is output. |
||||
|
||||
Assetic includes the following templating helper functions: |
||||
|
||||
* `assetic_image()` |
||||
* `assetic_javascripts()` |
||||
* `assetic_stylesheets()` |
||||
|
||||
Defining assets on the fly is a much more sophisticated technique and |
||||
therefore relies on services to do the heavy lifting. The main one being the |
||||
asset factory. |
||||
|
||||
### Asset Factory |
||||
|
||||
The asset factory knows how to create asset objects using only arrays and |
||||
scalar values as input. This is the same string syntax used by the `assetic_*` |
||||
template helper functions. |
||||
|
||||
use Assetic\Factory\AssetFactory; |
||||
|
||||
$factory = new AssetFactory('/path/to/web'); |
||||
$js = $factory->createAsset(array( |
||||
'js/jquery.js', |
||||
'js/jquery.plugin.js', |
||||
'js/application.js', |
||||
)); |
||||
|
||||
### Filter Manager |
||||
|
||||
You can also apply filters to asset created by the factory. To do this you |
||||
must setup a `FilterManager`, which organizes filters by a name. |
||||
|
||||
use Assetic\FilterManager; |
||||
use Assetic\Filter\GoogleClosure\ApiFilter as ClosureFilter; |
||||
|
||||
$fm = new FilterManager(); |
||||
$fm->set('closure', new ClosureFilter()); |
||||
$factory->setFilterManager($fm); |
||||
|
||||
$js = $factory->createAsset('js/*', 'closure'); |
||||
|
||||
This code creates an instance of the Google Closure Compiler filter and |
||||
assigns it the name `closure` using a filter manager. This filter manager is |
||||
then injected into the asset factory, making the filter available as `closure` |
||||
when creating assets. |
||||
|
||||
### Debug Mode |
||||
|
||||
The asset factory also introduces the concept of a debug mode. This mode |
||||
allows you to omit certain filters from assets the factory creates depending |
||||
on whether it is enabled or not. |
||||
|
||||
For example, the YUI Compressor is awesome, but it is only appropriate in a |
||||
production environment as it is very difficult to debug minified Javascript. |
||||
|
||||
use Asset\Factory\AssetFactory; |
||||
|
||||
$factory = new AssetFactory('/path/to/web', true); // debug mode is on |
||||
$factory->setFilterManager($fm); |
||||
$js = $factory->createAsset('js/*', '?closure'); |
||||
|
||||
By prefixing the `closure` filter's name with a question mark, we are telling |
||||
the factory this filter is optional and should only be applied with debug mode |
||||
is off. |
||||
|
||||
### Asset Manager and Asset References |
||||
|
||||
The asset factory provides another special string syntax that allows you to |
||||
reference assets you defined elsewhere. These are called "asset references" |
||||
and involve an asset manager which, similar to the filter manager, organizes |
||||
assets by name. |
||||
|
||||
use Assetic\AssetManager; |
||||
use Assetic\Asset\FileAsset; |
||||
use Assetic\Factory\AssetFactory; |
||||
|
||||
$am = new AssetManager(); |
||||
$am->set('jquery', new FileAsset('/path/to/jquery.js')); |
||||
|
||||
$factory = new AssetFactory('/path/to/web'); |
||||
$factory->setAssetManager($am); |
||||
|
||||
$js = $factory->createAsset(array( |
||||
'@jquery', |
||||
'js/application.js', |
||||
)); |
||||
|
||||
### Extracting Assets from Templates |
||||
|
||||
Once you've defined a set of assets in your templates you must use the |
||||
"formula loader" service to extract these asset definitions. |
||||
|
||||
use Assetic\Factory\Loader\FunctionCallsFormulaLoader; |
||||
use Assetic\Factory\Resource\FileResource; |
||||
|
||||
$loader = new FunctionCallsFormulaLoader($factory); |
||||
$formulae = $loader->load(new FileResource('/path/to/template.php')); |
||||
|
||||
These asset formulae aren't much use by themselves. They each include just |
||||
enough information for the asset factory to create the intended asset object. |
||||
In order for these to be useful they must be wrapped in the special |
||||
`LazyAssetManager`. |
||||
|
||||
### The Lazy Asset Manager |
||||
|
||||
This service is a composition of the asset factory and one or more formula |
||||
loaders. It acts as the glue between these services behind the scenes, but can |
||||
be used just like a normal asset manager on the surface. |
||||
|
||||
use Assetic\Asset\FileAsset; |
||||
use Assetic\Factory\LazyAssetManager; |
||||
use Assetic\Factory\Loader\FunctionCallsFormulaLoader; |
||||
use Assetic\Factory\Resource\DirectoryResource; |
||||
|
||||
$am = new LazyAssetManager($factory); |
||||
$am->set('jquery', new FileAsset('/path/to/jquery.js')); |
||||
$am->setLoader('php', new FunctionCallsFormulaLoader($factory)); |
||||
$am->addResource(new DirectoryResource('/path/to/templates', '/\.php$/'), 'php'); |
||||
|
||||
### Asset Writer |
||||
|
||||
Finally, once you've create an asset manager that knows about every asset |
||||
you've defined in your templates, you must use an asset writer to actually |
||||
create the files your templates are going to be referencing. |
||||
|
||||
use Assetic\AssetWriter; |
||||
|
||||
$writer = new AssetWriter('/path/to/web'); |
||||
$writer->writeManagerAssets($am); |
||||
|
||||
After running this script, all of the assets in your asset manager will be |
||||
loaded into memory, filtered with their configured filters and dumped to your |
||||
web directory as static files, ready to be served. |
||||
@ -0,0 +1,7 @@ |
||||
Table Of Contents |
||||
----------------- |
||||
|
||||
1. [Introduction](introduction.md) |
||||
2. [Building and Dumping Assets](build.md) |
||||
3. [Basic Concepts](concepts.md) |
||||
4. [Defining Assets "On The Fly"](define.md) |
||||
@ -0,0 +1,21 @@ |
||||
What is Assetic? |
||||
---------------- |
||||
|
||||
Assetic is an asset management framework for PHP 5.3. Assetic enables you to |
||||
use a variety of third party tools that will help bring order to your |
||||
application's Javascripts, stylesheets and images. |
||||
|
||||
How Do I Use Assetic? |
||||
--------------------- |
||||
|
||||
There are two distinct approaches you can take when using Assetic: |
||||
|
||||
1. Build, dump and output assets in PHP files that you reference directly |
||||
from your templates |
||||
2. Defining assets in your templates ("on the fly") and use a loader to |
||||
extract, dump and output them |
||||
|
||||
The first approach is simpler, but the second, with all its moving parts, |
||||
offers more flexibility and opportunity for optimization. |
||||
|
||||
Next: [Building and Dumping Assets](build.md) |
||||
@ -0,0 +1,30 @@ |
||||
アセットのビルドとダンプ |
||||
--------------------------- |
||||
|
||||
Asseticを使う一番単純な方法は、次の2ステップからなります。 |
||||
|
||||
1. 公開領域内にPHPスクリプトを作成し、Assetic OOP APIを使用してアセットの作成・出力を行う |
||||
2. テンプレートから上記のファイルを参照する |
||||
|
||||
例えば、公開領域内に`assets/javascripts.php`ファイルを作成し、 |
||||
下記のようなコードを記述します。 |
||||
|
||||
use Assetic\Asset\AssetCollection; |
||||
use Assetic\Asset\FileAsset; |
||||
use Assetic\Filter\Yui\JsCompressorFilter as YuiCompressorFilter; |
||||
|
||||
$js = new AssetCollection(array( |
||||
new FileAsset(__DIR__.'/jquery.js'), |
||||
new FileAsset(__DIR__.'/application.js'), |
||||
), array( |
||||
new YuiCompressorFilter('/path/to/yuicompressor.jar'), |
||||
)); |
||||
|
||||
header('Content-Type: application/js'); |
||||
echo $js->dump(); |
||||
|
||||
HTMLテンプレート側では、単に`<script>`タグを用いて、生成されたJavascriptをインクルードすることになります。 |
||||
|
||||
<script src="/assets/javascripts.php"></script> |
||||
|
||||
Next: [コンセプト](concepts.md) |
||||
@ -0,0 +1,121 @@ |
||||
Assetic OOP APIを使用するためには、まず、[アセット」と「フィルタ」の2つの重要なコンセプトを理解する必要があります。 |
||||
|
||||
### アセット |
||||
|
||||
アセットとは、読み込み、及びダンプが可能な、コンテンツとメタデータを内包しているオブジェクトの事を指します。 |
||||
大体の場合において3つのカテゴリー、すなわち、Javascriptとスタイルシート、画像のどれかに属することになるでしょう。 |
||||
読み込みの方法としては、ファイルシステムからがほとんどですが、 |
||||
HTTPやデータベース経由でも、文字列としてでも読み込みが可能で、事実上あらゆるものが読み込み可能です。 |
||||
Asseticのアセットインターフェースを満足させさえすれば良いのです。 |
||||
|
||||
|
||||
### フィルタ |
||||
|
||||
フィルタは、アセットが読み込まれる、かつ/もしくは、ダンプされる際に、 |
||||
アセットコンテンツに対して作用するオブジェクトです。 |
||||
アセットと同様に、Asseticのフィルタインターフェースを実装することで、 |
||||
どのような作用も可能になります。 |
||||
|
||||
フィルタを用いて、アセットに適用できるツール群の一覧です。 |
||||
|
||||
* CoffeeScript |
||||
* CssEmbed |
||||
* CssMin |
||||
* Google Closure Compiler |
||||
* jpegoptim |
||||
* jpegtran |
||||
* Less |
||||
* LessPHP |
||||
* optipng |
||||
* Packager |
||||
* pngout |
||||
* SASS |
||||
* Sprockets (version 1) |
||||
* Stylus |
||||
* YUI Compressor |
||||
|
||||
|
||||
### アセットとフィルタの使用 |
||||
|
||||
まずはアセットオブジェクトを作成することから始まります。 |
||||
多くの場合は`FileAsset`をインスタンス化し、ファイルシステムのパスを第一引数に渡します。 |
||||
|
||||
$asset = new Assetic\Asset\FileAsset('/path/to/main.css'); |
||||
|
||||
アセットオブジェクトを作成したら、`ensureFilter()`を呼び、フィルタを追加します。 |
||||
例えば、アセットコンテンツにYUI Compressorを適用してみましょう。 |
||||
|
||||
$yui = new Assetic\Filter\Yui\CssCompressorFilter('/path/to/yui.jar'); |
||||
$asset->ensureFilter($yui); |
||||
|
||||
任意のフィルタを追加したら、完成したアセットをブラウザに出力してみましょう。 |
||||
|
||||
header('Content-Type: text/css'); |
||||
echo $asset->dump(); |
||||
|
||||
### アセットコレクション |
||||
|
||||
1つのファイルに同じ種類のアセットをまとめて、不要なHTTPリクエストを抑えてみるのも良いでしょう。 |
||||
Asseticでは`AsseticColletion`クラスを使用することで可能となります。 |
||||
Assetic内部的には、このクラス自体は他のアセットと同様に、アセットインターフェースを実装したものですが、 |
||||
複数のアセットを1つにまとめることが可能になります。 |
||||
|
||||
use Assetic\Asset\AssetCollection; |
||||
|
||||
$asset = new AssetCollection(array( |
||||
new FileAsset('/path/to/js/jquery.js'), |
||||
new FileAsset('/path/to/js/jquery.plugin.js'), |
||||
new FileAsset('/path/to/js/application.js'), |
||||
)); |
||||
|
||||
### ネストしたアセットコレクション |
||||
|
||||
コレクションクラス自体がアセットインターフェースを実装し、コレクション内のアセットも同様に |
||||
アセットインターフェースを実装しているので、簡単にネストすることができます。 |
||||
|
||||
use Assetic\Asset\AssetCollection; |
||||
use Assetic\Asset\GlobAsset; |
||||
use Assetic\Asset\HttpAsset; |
||||
|
||||
$asset = new AssetCollection(array( |
||||
new HttpAsset('http://example.com/jquery.min.js'), |
||||
new GlobAsset('/path/to/js/*'), |
||||
)); |
||||
|
||||
`HttpAsset`は、HTTP経由でファイルを読み込むアセットクラス。 |
||||
`GlobAsset`は、ファイルシステムのglobを基にファイル群を読み込むアセットコレクションクラス。 |
||||
両者ともにアセットインターフェースを実装しています。 |
||||
|
||||
このネストしたアセットコレクションという概念は、コレクションそれぞれに異なる |
||||
フィルタ群を適用しようとしたときに、効果を発揮します。 |
||||
例えば、スタイルシートがSAASで記述されたものと、vanilla CSSを用いて記述されたものからなる |
||||
アプリケーションを考えた場合、次のようにして、全てを1つのシームレスなCSSアセットにまとめることができます。 |
||||
|
||||
use Assetic\Asset\AssetCollection; |
||||
use Assetic\Asset\GlobAsset; |
||||
use Assetic\Filter\SassFilter; |
||||
use Assetic\Filter\Yui\CssCompressorFilter; |
||||
|
||||
$css = new AssetCollection(array( |
||||
new GlobAsset('/path/to/sass/*.sass', array(new SassFilter())), |
||||
new GlobAsset('/path/to/css/*.css'), |
||||
), array( |
||||
new YuiCompressorFilter('/path/to/yuicompressor.jar'), |
||||
)); |
||||
|
||||
上記の例では、1つにまとめられたCSSを、さらにYUI compressorフィルタを適用することで、全体を圧縮しています。 |
||||
|
||||
### アセットコレクションのイテレーション |
||||
|
||||
アセットコレクションは、旧来のPHP配列のように、イテレートできます。 |
||||
|
||||
echo "Source paths:\n"; |
||||
foreach ($collection as $asset) { |
||||
echo ' - '.$asset->getSourcePath()."\n"; |
||||
} |
||||
|
||||
アセットコレクションのイテレーションは再帰的で、「葉」にあたるアセットの取得を行います。 |
||||
また、気の利いたフィルタを内蔵しているので、同じアセットがコレクション内に複数存在する場合でも、 |
||||
一度だけのインクルードが保証されます。 |
||||
|
||||
Next: [アセットを「オンザフライ」で定義する](define.md) |
||||
@ -0,0 +1,140 @@ |
||||
アセットの「オンザフライ」な定義 |
||||
---------------------------------------- |
||||
|
||||
Asseticの使用方法二つ目は、独立したPHPファイルを使用する代わりに、 |
||||
テンプレートで「オンザフライ」にアセット定義をする方法です。 |
||||
このアプローチでは、PHPテンプレートは下記のようになります。 |
||||
|
||||
<script src="<?php echo assetic_javascripts('js/*', 'yui_js') ?>"></script> |
||||
|
||||
`assetic_javascripts()`の呼び出しは2つの目的を兼ねています。 |
||||
まず、「フォーミュラローダー」により走査され、アセットの構築、ダンプ、及び出力を行うための「フォーミュラ(処方箋)」が抽出されます。 |
||||
また、テンプレートのレンダー時にも実行され、アセットの出力パスが出力されます。 |
||||
|
||||
Asseticには下記のようなヘルパー関数があります。 |
||||
|
||||
* `assetic_image()` |
||||
* `assetic_javascripts()` |
||||
* `assetic_stylesheets()` |
||||
|
||||
アセットをオンザフライに定義するということは、より高度なテクニックであり、 |
||||
そのため、重い仕事をするサービスに依存することになります。 |
||||
そのうちの重要なものがアセットファクトリです。 |
||||
|
||||
### アセットファクトリ |
||||
|
||||
アセットファクトリは、アセットオブジェクトを、配列とスカラ値のみから、 |
||||
どのように作成するのか把握しています。 |
||||
`assetic_*`ヘルパー関数で使用する記法と同様のものとなります。 |
||||
|
||||
use Assetic\Factory\AssetFactory; |
||||
|
||||
$factory = new AssetFactory('/path/to/web'); |
||||
$js = $factory->createAsset(array( |
||||
'js/jquery.js', |
||||
'js/jquery.plugin.js', |
||||
'js/application.js', |
||||
)); |
||||
|
||||
### フィルタマネージャー |
||||
|
||||
ファクトリによって作成されたアセットに対しても、フィルタを適用することができます。 |
||||
そのためには、`FilterManager`を設定して、名前を定義しフィルタを構成します。 |
||||
|
||||
use Assetic\FilterManager; |
||||
use Assetic\Filter\GoogleClosure\ApiFilter as ClosureFilter; |
||||
|
||||
$fm = new FilterManager(); |
||||
$fm->set('closure', new ClosureFilter()); |
||||
$factory->setFilterManager($fm); |
||||
|
||||
$js = $factory->createAsset('js/*', 'closure'); |
||||
|
||||
上記の例では、Google Closure Compilerフィルタをインスタンス化し、 |
||||
フィルタマネージャーを通じて`closure`という名前をつけています。 |
||||
このフィルタマネージャーをアセットファクトリに渡すことで、 |
||||
アセット作成時には、`closure`という名前でフィルタを使用できるようになります。 |
||||
|
||||
### デバッグモード |
||||
|
||||
アセットファクトリは、デバッグモードというコンセプトも取り入れており、 |
||||
デバッグモードの設定により、ファクトリが作成するアセットから、 |
||||
特定のフィルタを除外することができます。 |
||||
|
||||
たとえば、YUI Compressorは大変素晴らしいのですが、圧縮されたJavascriptを |
||||
デバッグするのは大変難しく、プロダクション環境でのみの使用が適切でしょう。 |
||||
|
||||
use Asset\Factory\AssetFactory; |
||||
|
||||
$factory = new AssetFactory('/path/to/web', true); // デバッグモードON |
||||
$factory->setFilterManager($fm); |
||||
$js = $factory->createAsset('js/*', '?closure'); |
||||
|
||||
フィルタ名`closure`の前にクエスチョンマークを記述すると、ファクトリに対して、 |
||||
このフィルタはオプションであり、 |
||||
デバッグモードがOFFの時にのみ適用するように通知することができます。 |
||||
|
||||
### アセットマネージャーとアセットリファレンス |
||||
|
||||
アセットファクトリにはもう一つ特別な記法があり、別の場所で定義した |
||||
アセットを参照することができるようになります。 |
||||
これを「アセットリファレンス」と呼び、アセットマネージャーを通じて、 |
||||
フィルタマネージャーと同様の、名前によるアセットの構成が可能です。 |
||||
|
||||
use Assetic\AssetManager; |
||||
use Assetic\Asset\FileAsset; |
||||
use Assetic\Factory\AssetFactory; |
||||
|
||||
$am = new AssetManager(); |
||||
$am->set('jquery', new FileAsset('/path/to/jquery.js')); |
||||
|
||||
$factory = new AssetFactory('/path/to/web'); |
||||
$factory->setAssetManager($am); |
||||
|
||||
$js = $factory->createAsset(array( |
||||
'@jquery', |
||||
'js/application.js', |
||||
)); |
||||
|
||||
### テンプレートからのアセット抽出 |
||||
|
||||
テンプレート内でアセット群を定義したら、「フォーミュラローダー」サービスを使用して、 |
||||
アセットの定義を抽出します。 |
||||
|
||||
use Assetic\Factory\Loader\FunctionCallsFormulaLoader; |
||||
use Assetic\Factory\Resource\FileResource; |
||||
|
||||
$loader = new FunctionCallsFormulaLoader($factory); |
||||
$formulae = $loader->load(new FileResource('/path/to/template.php')); |
||||
|
||||
これらのフォーミュラ自体は、それ自体で使途はあまりなく、 |
||||
アセットファクトリが目的のアセットオブジェクトを作成するに足る情報しか持っていません。 |
||||
`LazyAssetManager`でラップすることで有益なものとなります。 |
||||
|
||||
### レイジーなアセットマネージャー |
||||
|
||||
このサービスは、アセットファクトリと、1つ以上のフォーミュラローダーから成っており、 |
||||
裏方のサービス間のグルとして動作しますが、表面上では、通常のアセットマネージャーと同じように使用することができます。 |
||||
|
||||
use Assetic\Asset\FileAsset; |
||||
use Assetic\Factory\LazyAssetManager; |
||||
use Assetic\Factory\Loader\FunctionCallsFormulaLoader; |
||||
use Assetic\Factory\Resource\DirectoryResource; |
||||
|
||||
$am = new LazyAssetManager($factory); |
||||
$am->set('jquery', new FileAsset('/path/to/jquery.js')); |
||||
$am->setLoader('php', new FunctionCallsFormulaLoader($factory)); |
||||
$am->addResource(new DirectoryResource('/path/to/templates', '/\.php$/'), 'php'); |
||||
|
||||
### アセットライター |
||||
|
||||
作成したアセットマネージャーが、テンプレート内で定義した全てのアセットを把握したら、 |
||||
アセットライターを使用して、テンプレートが参照することになる実際のファイルを作成します。 |
||||
|
||||
use Assetic\AssetWriter; |
||||
|
||||
$writer = new AssetWriter('/path/to/web'); |
||||
$writer->writeManagerAssets($am); |
||||
|
||||
上記のスクリプトを実行すると、アセットマネージャー内のすべてのアセットがメモリに読み込まれ、 |
||||
指定したフィルタが適用された後、公開領域に静的ファイルとしてダンプされ、準備完了となります。 |
||||
@ -0,0 +1,7 @@ |
||||
目次 |
||||
----- |
||||
|
||||
1. [イントロダクション](introduction.md) |
||||
2. [アセットの構築とダンプ](build.md) |
||||
3. [コンセプト](concepts.md) |
||||
4. [アセットを「オンザフライ」で定義する](define.md) |
||||
@ -0,0 +1,18 @@ |
||||
Asseticとは |
||||
----------------- |
||||
|
||||
Asseticは、PHP5.3用のアセット管理フレームワークです。 |
||||
Asseticを導入することで、Javascriptやスタイルシート、画像をコントロールする |
||||
様々なサードパーティー製のツールを使用できるようになります。 |
||||
|
||||
Asseticの使用方法 |
||||
--------------------- |
||||
|
||||
2つの異なるアプローチがあります。 |
||||
|
||||
1. アセットのビルド、ダンプ、出力をPHPファイルで行い、テンプレートからそのファイルを直接参照する方法 |
||||
2. テンプレート内でアセットを(「オンザフライ」に)定義し、抽出やダンプ、出力にローダーを使用する方法 |
||||
|
||||
前者はいくらかシンプルである一方、後者は動的で柔軟性に富み、最適化が可能となります。 |
||||
|
||||
Next: [アセットの構築とダンプ](build.md) |
||||
@ -0,0 +1,36 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
||||
<phpunit bootstrap="./tests/bootstrap.php" colors="true"> |
||||
<testsuites> |
||||
<testsuite name="Assetic Test Suite"> |
||||
<directory suffix="Test.php">./tests/Assetic/Test/</directory> |
||||
</testsuite> |
||||
</testsuites> |
||||
|
||||
<php> |
||||
<!-- <server name="CLOSURE_JAR" value="/path/to/google-closure/compiler.jar" /> --> |
||||
<!-- <server name="COFFEE_BIN" value="/path/to/coffee" /> --> |
||||
<!-- <server name="NODE_BIN" value="/path/to/node" /> --> |
||||
<!-- <server name="NODE_PATH" value="/path/to/node/lib" /> --> |
||||
<server name="LESSPHP" value="vendor/lessphp/lessc.inc.php" /> |
||||
<!-- <server name="SASS_BIN" value="/path/to/sass" /> --> |
||||
<!-- <server name="COMPASS_BIN" value="/path/to/compass" /> --> |
||||
<!-- <server name="RUBY_BIN" value="/path/to/ruby" /> --> |
||||
<!-- <server name="SPROCKETS_LIB" value="/path/to/sprockets/lib" /> --> |
||||
<server name="TWIG_LIB" value="vendor/twig/lib" /> |
||||
<!-- <server name="YUI_COMPRESSOR_JAR" value="/path/to/yuicompressor-2.4.2.jar" /> --> |
||||
<!-- <server name="OPTIPNG_BIN" value="/path/to/optipng" /> --> |
||||
<!-- <server name="JPEGOPTIM_BIN" value="/path/to/jpegoptim" /> --> |
||||
<!-- <server name="JPEGTRAN_BIN" value="/path/to/jpegtran" /> --> |
||||
<!-- <server name="PNGOUT_BIN" value="/path/to/pngout" /> --> |
||||
<server name="CSSMIN" value="vendor/cssmin/source/CssMin.php" /> |
||||
<!-- <server name="CSSEMBED_JAR" value="/path/to/cssembed.jar" /> --> |
||||
<server name="PACKAGER" value="vendor/packager/packager.php" /> |
||||
</php> |
||||
|
||||
<filter> |
||||
<whitelist> |
||||
<directory suffix=".php">./src/Assetic/</directory> |
||||
</whitelist> |
||||
</filter> |
||||
</phpunit> |
||||
@ -0,0 +1,36 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
||||
<phpunit bootstrap="./tests/bootstrap.php" colors="true"> |
||||
<testsuites> |
||||
<testsuite name="Assetic Test Suite"> |
||||
<directory suffix="Test.php">./tests/Assetic/Test/</directory> |
||||
</testsuite> |
||||
</testsuites> |
||||
|
||||
<php> |
||||
<!-- <server name="CLOSURE_JAR" value="/path/to/google-closure/compiler.jar" /> --> |
||||
<!-- <server name="COFFEE_BIN" value="/path/to/coffee" /> --> |
||||
<!-- <server name="NODE_BIN" value="/path/to/node" /> --> |
||||
<!-- <server name="NODE_PATH" value="/path/to/node/lib" /> --> |
||||
<!-- <server name="LESSPHP" value="/path/to/lessphp/lessc.inc.php" /> --> |
||||
<!-- <server name="SASS_BIN" value="/path/to/sass" /> --> |
||||
<!-- <server name="COMPASS_BIN" value="/path/to/compass" /> --> |
||||
<!-- <server name="RUBY_BIN" value="/path/to/ruby" /> --> |
||||
<!-- <server name="SPROCKETS_LIB" value="/path/to/sprockets/lib" /> --> |
||||
<!-- <server name="TWIG_LIB" value="/path/to/twig/lib" /> --> |
||||
<!-- <server name="YUI_COMPRESSOR_JAR" value="/path/to/yuicompressor-2.4.2.jar" /> --> |
||||
<!-- <server name="OPTIPNG_BIN" value="/path/to/optipng" /> --> |
||||
<!-- <server name="JPEGOPTIM_BIN" value="/path/to/jpegoptim" /> --> |
||||
<!-- <server name="JPEGTRAN_BIN" value="/path/to/jpegtran" /> --> |
||||
<!-- <server name="PNGOUT_BIN" value="/path/to/pngout" /> --> |
||||
<!-- <server name="CSSMIN" value="/path/to/cssmin/source/CssMin.php" /> --> |
||||
<!-- <server name="CSSEMBED_JAR" value="/path/to/cssembed.jar" /> --> |
||||
<!-- <server name="PACKAGER" value="/path/to/packager.php" /> --> |
||||
</php> |
||||
|
||||
<filter> |
||||
<whitelist> |
||||
<directory suffix=".php">./src/Assetic/</directory> |
||||
</whitelist> |
||||
</filter> |
||||
</phpunit> |
||||
@ -0,0 +1,149 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Asset; |
||||
|
||||
use Assetic\Cache\CacheInterface; |
||||
use Assetic\Filter\FilterInterface; |
||||
use Assetic\Filter\HashableInterface; |
||||
|
||||
/** |
||||
* Caches an asset to avoid the cost of loading and dumping. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class AssetCache implements AssetInterface |
||||
{ |
||||
private $asset; |
||||
private $cache; |
||||
|
||||
public function __construct(AssetInterface $asset, CacheInterface $cache) |
||||
{ |
||||
$this->asset = $asset; |
||||
$this->cache = $cache; |
||||
} |
||||
|
||||
public function ensureFilter(FilterInterface $filter) |
||||
{ |
||||
$this->asset->ensureFilter($filter); |
||||
} |
||||
|
||||
public function getFilters() |
||||
{ |
||||
return $this->asset->getFilters(); |
||||
} |
||||
|
||||
public function clearFilters() |
||||
{ |
||||
$this->asset->clearFilters(); |
||||
} |
||||
|
||||
public function load(FilterInterface $additionalFilter = null) |
||||
{ |
||||
$cacheKey = self::getCacheKey($this->asset, $additionalFilter, 'load'); |
||||
if ($this->cache->has($cacheKey)) { |
||||
$this->asset->setContent($this->cache->get($cacheKey)); |
||||
|
||||
return; |
||||
} |
||||
|
||||
$this->asset->load($additionalFilter); |
||||
$this->cache->set($cacheKey, $this->asset->getContent()); |
||||
} |
||||
|
||||
public function dump(FilterInterface $additionalFilter = null) |
||||
{ |
||||
$cacheKey = self::getCacheKey($this->asset, $additionalFilter, 'dump'); |
||||
if ($this->cache->has($cacheKey)) { |
||||
return $this->cache->get($cacheKey); |
||||
} |
||||
|
||||
$content = $this->asset->dump($additionalFilter); |
||||
$this->cache->set($cacheKey, $content); |
||||
|
||||
return $content; |
||||
} |
||||
|
||||
public function getContent() |
||||
{ |
||||
return $this->asset->getContent(); |
||||
} |
||||
|
||||
public function setContent($content) |
||||
{ |
||||
$this->asset->setContent($content); |
||||
} |
||||
|
||||
public function getSourceRoot() |
||||
{ |
||||
return $this->asset->getSourceRoot(); |
||||
} |
||||
|
||||
public function getSourcePath() |
||||
{ |
||||
return $this->asset->getSourcePath(); |
||||
} |
||||
|
||||
public function getTargetPath() |
||||
{ |
||||
return $this->asset->getTargetPath(); |
||||
} |
||||
|
||||
public function setTargetPath($targetPath) |
||||
{ |
||||
$this->asset->setTargetPath($targetPath); |
||||
} |
||||
|
||||
public function getLastModified() |
||||
{ |
||||
return $this->asset->getLastModified(); |
||||
} |
||||
|
||||
/** |
||||
* Returns a cache key for the current asset. |
||||
* |
||||
* The key is composed of everything but an asset's content: |
||||
* |
||||
* * source root |
||||
* * source path |
||||
* * target url |
||||
* * last modified |
||||
* * filters |
||||
* |
||||
* @param AssetInterface $asset The asset |
||||
* @param FilterInterface $additionalFilter Any additional filter being applied |
||||
* @param string $salt Salt for the key |
||||
* |
||||
* @return string A key for identifying the current asset |
||||
*/ |
||||
private static function getCacheKey(AssetInterface $asset, FilterInterface $additionalFilter = null, $salt = '') |
||||
{ |
||||
if ($additionalFilter) { |
||||
$asset = clone $asset; |
||||
$asset->ensureFilter($additionalFilter); |
||||
} |
||||
|
||||
$cacheKey = $asset->getSourceRoot(); |
||||
$cacheKey .= $asset->getSourcePath(); |
||||
$cacheKey .= $asset->getTargetPath(); |
||||
$cacheKey .= $asset->getLastModified(); |
||||
|
||||
foreach ($asset->getFilters() as $filter) { |
||||
if ($filter instanceof HashableInterface) { |
||||
$cacheKey .= $filter->hash(); |
||||
} else { |
||||
$cacheKey .= serialize($filter); |
||||
} |
||||
} |
||||
|
||||
return md5($cacheKey.$salt); |
||||
} |
||||
} |
||||
@ -0,0 +1,326 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Asset; |
||||
|
||||
use Assetic\Filter\FilterCollection; |
||||
use Assetic\Filter\FilterInterface; |
||||
|
||||
/** |
||||
* A collection of assets. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class AssetCollection implements AssetInterface, \IteratorAggregate |
||||
{ |
||||
private $assets; |
||||
private $filters; |
||||
private $sourceRoot; |
||||
private $targetPath; |
||||
private $content; |
||||
private $clones; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param array $assets Assets for the current collection |
||||
* @param array $filters Filters for the current collection |
||||
* @param string $sourceRoot The root directory |
||||
*/ |
||||
public function __construct($assets = array(), $filters = array(), $sourceRoot = null) |
||||
{ |
||||
$this->assets = array(); |
||||
foreach ($assets as $asset) { |
||||
$this->add($asset); |
||||
} |
||||
|
||||
$this->filters = new FilterCollection($filters); |
||||
$this->sourceRoot = $sourceRoot; |
||||
$this->clones = new \SplObjectStorage(); |
||||
} |
||||
|
||||
/** |
||||
* Adds an asset to the current collection. |
||||
* |
||||
* @param AssetInterface $asset An asset |
||||
*/ |
||||
public function add(AssetInterface $asset) |
||||
{ |
||||
$this->assets[] = $asset; |
||||
} |
||||
|
||||
public function all() |
||||
{ |
||||
return $this->assets; |
||||
} |
||||
|
||||
public function ensureFilter(FilterInterface $filter) |
||||
{ |
||||
$this->filters->ensure($filter); |
||||
} |
||||
|
||||
public function getFilters() |
||||
{ |
||||
return $this->filters->all(); |
||||
} |
||||
|
||||
public function clearFilters() |
||||
{ |
||||
$this->filters->clear(); |
||||
} |
||||
|
||||
public function load(FilterInterface $additionalFilter = null) |
||||
{ |
||||
// loop through leaves and load each asset |
||||
$parts = array(); |
||||
foreach ($this as $asset) { |
||||
$asset->load($additionalFilter); |
||||
$parts[] = $asset->getContent(); |
||||
} |
||||
|
||||
$this->content = implode("\n", $parts); |
||||
} |
||||
|
||||
public function dump(FilterInterface $additionalFilter = null) |
||||
{ |
||||
// loop through leaves and dump each asset |
||||
$parts = array(); |
||||
foreach ($this as $asset) { |
||||
$parts[] = $asset->dump($additionalFilter); |
||||
} |
||||
|
||||
return implode("\n", $parts); |
||||
} |
||||
|
||||
public function getContent() |
||||
{ |
||||
return $this->content; |
||||
} |
||||
|
||||
public function setContent($content) |
||||
{ |
||||
$this->content = $content; |
||||
} |
||||
|
||||
public function getSourceRoot() |
||||
{ |
||||
return $this->sourceRoot; |
||||
} |
||||
|
||||
public function getSourcePath() |
||||
{ |
||||
} |
||||
|
||||
public function getTargetPath() |
||||
{ |
||||
return $this->targetPath; |
||||
} |
||||
|
||||
public function setTargetPath($targetPath) |
||||
{ |
||||
$this->targetPath = $targetPath; |
||||
} |
||||
|
||||
/** |
||||
* Returns the highest last-modified value of all assets in the current collection. |
||||
* |
||||
* @return integer|null A UNIX timestamp |
||||
*/ |
||||
public function getLastModified() |
||||
{ |
||||
if (!count($this->assets)) { |
||||
return; |
||||
} |
||||
|
||||
$mapper = function (AssetInterface $asset) { |
||||
return $asset->getLastModified(); |
||||
}; |
||||
|
||||
return max(array_map($mapper, $this->assets)); |
||||
} |
||||
|
||||
/** |
||||
* Returns an iterator for looping recursively over unique leaves. |
||||
*/ |
||||
public function getIterator() |
||||
{ |
||||
return new \RecursiveIteratorIterator(new AssetCollectionFilterIterator(new AssetCollectionIterator($this, $this->clones))); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Asset collection filter iterator. |
||||
* |
||||
* The filter iterator is responsible for de-duplication of leaf assets based |
||||
* on both strict equality and source URL. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
* @access private |
||||
*/ |
||||
class AssetCollectionFilterIterator extends \RecursiveFilterIterator |
||||
{ |
||||
private $visited; |
||||
private $sources; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param AssetCollectionIterator $iterator The inner iterator |
||||
* @param array $visited An array of visited asset objects |
||||
* @param array $sources An array of visited source strings |
||||
*/ |
||||
public function __construct(AssetCollectionIterator $iterator, array $visited = array(), array $sources = array()) |
||||
{ |
||||
parent::__construct($iterator); |
||||
|
||||
$this->visited = $visited; |
||||
$this->sources = $sources; |
||||
} |
||||
|
||||
/** |
||||
* Determines whether the current asset is a duplicate. |
||||
* |
||||
* De-duplication is performed based on either strict equality or by |
||||
* matching sources. |
||||
* |
||||
* @return Boolean Returns true if we have not seen this asset yet |
||||
*/ |
||||
public function accept() |
||||
{ |
||||
$asset = $this->getInnerIterator()->current(true); |
||||
$duplicate = false; |
||||
|
||||
// check strict equality |
||||
if (in_array($asset, $this->visited, true)) { |
||||
$duplicate = true; |
||||
} else { |
||||
$this->visited[] = $asset; |
||||
} |
||||
|
||||
// check source |
||||
$sourceRoot = $asset->getSourceRoot(); |
||||
$sourcePath = $asset->getSourcePath(); |
||||
if ($sourceRoot && $sourcePath) { |
||||
$source = $sourceRoot.'/'.$sourcePath; |
||||
if (in_array($source, $this->sources)) { |
||||
$duplicate = true; |
||||
} else { |
||||
$this->sources[] = $source; |
||||
} |
||||
} |
||||
|
||||
return !$duplicate; |
||||
} |
||||
|
||||
/** |
||||
* Passes visited objects and source URLs to the child iterator. |
||||
*/ |
||||
public function getChildren() |
||||
{ |
||||
return new self($this->getInnerIterator()->getChildren(), $this->visited, $this->sources); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Iterates over an asset collection. |
||||
* |
||||
* The iterator is responsible for cascading filters and target URL patterns |
||||
* from parent to child assets. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
* @access private |
||||
*/ |
||||
class AssetCollectionIterator implements \RecursiveIterator |
||||
{ |
||||
private $assets; |
||||
private $filters; |
||||
private $output; |
||||
private $clones; |
||||
|
||||
public function __construct(AssetCollection $coll, \SplObjectStorage $clones) |
||||
{ |
||||
$this->assets = $coll->all(); |
||||
$this->filters = $coll->getFilters(); |
||||
$this->output = $coll->getTargetPath(); |
||||
$this->clones = $clones; |
||||
|
||||
if (false === $pos = strpos($this->output, '.')) { |
||||
$this->output .= '_*'; |
||||
} else { |
||||
$this->output = substr($this->output, 0, $pos).'_*'.substr($this->output, $pos); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Returns a copy of the current asset with filters and a target URL applied. |
||||
* |
||||
* @param Boolean $raw Returns the unmodified asset if true |
||||
*/ |
||||
public function current($raw = false) |
||||
{ |
||||
$asset = current($this->assets); |
||||
|
||||
if ($raw) { |
||||
return $asset; |
||||
} |
||||
|
||||
// clone once |
||||
if (!isset($this->clones[$asset])) { |
||||
$clone = $this->clones[$asset] = clone $asset; |
||||
|
||||
// generate a target path based on asset name |
||||
$name = sprintf('%s_%d', pathinfo($asset->getSourcePath(), PATHINFO_FILENAME) ?: 'part', $this->key() + 1); |
||||
$clone->setTargetPath(str_replace('*', $name, $this->output)); |
||||
} else { |
||||
$clone = $this->clones[$asset]; |
||||
} |
||||
|
||||
// cascade filters |
||||
foreach ($this->filters as $filter) { |
||||
$clone->ensureFilter($filter); |
||||
} |
||||
|
||||
return $clone; |
||||
} |
||||
|
||||
public function key() |
||||
{ |
||||
return key($this->assets); |
||||
} |
||||
|
||||
public function next() |
||||
{ |
||||
return next($this->assets); |
||||
} |
||||
|
||||
public function rewind() |
||||
{ |
||||
return reset($this->assets); |
||||
} |
||||
|
||||
public function valid() |
||||
{ |
||||
return false !== current($this->assets); |
||||
} |
||||
|
||||
public function hasChildren() |
||||
{ |
||||
return current($this->assets) instanceof AssetCollection; |
||||
} |
||||
|
||||
/** |
||||
* @uses current() |
||||
*/ |
||||
public function getChildren() |
||||
{ |
||||
return new self($this->current(), $this->clones); |
||||
} |
||||
} |
||||
@ -0,0 +1,135 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Asset; |
||||
|
||||
use Assetic\Filter\FilterInterface; |
||||
|
||||
/** |
||||
* An asset has a mutable URL and content and can be loaded and dumped. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
interface AssetInterface |
||||
{ |
||||
/** |
||||
* Ensures the current asset includes the supplied filter. |
||||
* |
||||
* @param FilterInterface $filter A filter |
||||
*/ |
||||
public function ensureFilter(FilterInterface $filter); |
||||
|
||||
/** |
||||
* Returns an array of filters currently applied. |
||||
* |
||||
* @return array An array of filters |
||||
*/ |
||||
public function getFilters(); |
||||
|
||||
/** |
||||
* Clears all filters from the current asset. |
||||
*/ |
||||
public function clearFilters(); |
||||
|
||||
/** |
||||
* Loads the asset into memory and applies load filters. |
||||
* |
||||
* You may provide an additional filter to apply during load. |
||||
* |
||||
* @param FilterInterface $additionalFilter An additional filter |
||||
*/ |
||||
public function load(FilterInterface $additionalFilter = null); |
||||
|
||||
/** |
||||
* Applies dump filters and returns the asset as a string. |
||||
* |
||||
* You may provide an additional filter to apply during dump. |
||||
* |
||||
* Dumping an asset should not change its state. |
||||
* |
||||
* If the current asset has not been loaded yet, it should be |
||||
* automatically loaded at this time. |
||||
* |
||||
* @param FilterInterface $additionalFilter An additional filter |
||||
* |
||||
* @return string The filtered content of the current asset |
||||
*/ |
||||
public function dump(FilterInterface $additionalFilter = null); |
||||
|
||||
/** |
||||
* Returns the loaded content of the current asset. |
||||
* |
||||
* @return string The content |
||||
*/ |
||||
public function getContent(); |
||||
|
||||
/** |
||||
* Sets the content of the current asset. |
||||
* |
||||
* Filters can use this method to change the content of the asset. |
||||
* |
||||
* @param string $content The asset content |
||||
*/ |
||||
public function setContent($content); |
||||
|
||||
/** |
||||
* Returns an absolute path or URL to the source asset's root directory. |
||||
* |
||||
* This value should be an absolute path to a directory in the filesystem, |
||||
* an absolute URL with no path, or null. |
||||
* |
||||
* For example: |
||||
* |
||||
* * '/path/to/web' |
||||
* * 'http://example.com' |
||||
* * null |
||||
* |
||||
* @return string|null The asset's root |
||||
*/ |
||||
public function getSourceRoot(); |
||||
|
||||
/** |
||||
* Returns the relative path for the source asset. |
||||
* |
||||
* This value can be combined with the asset's source root (if both are |
||||
* non-null) to get something compatible with file_get_contents(). |
||||
* |
||||
* For example: |
||||
* |
||||
* * 'js/main.js' |
||||
* * 'main.js' |
||||
* * null |
||||
* |
||||
* @return string|null The source asset path |
||||
*/ |
||||
public function getSourcePath(); |
||||
|
||||
/** |
||||
* Returns the URL for the current asset. |
||||
* |
||||
* @return string|null A web URL where the asset will be dumped |
||||
*/ |
||||
public function getTargetPath(); |
||||
|
||||
/** |
||||
* Sets the URL for the current asset. |
||||
* |
||||
* @param string $targetPath A web URL where the asset will be dumped |
||||
*/ |
||||
public function setTargetPath($targetPath); |
||||
|
||||
/** |
||||
* Returns the time the current asset was last modified. |
||||
* |
||||
* @return integer|null A UNIX timestamp |
||||
*/ |
||||
public function getLastModified(); |
||||
} |
||||
@ -0,0 +1,118 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Asset; |
||||
|
||||
use Assetic\AssetManager; |
||||
use Assetic\Filter\FilterInterface; |
||||
|
||||
/** |
||||
* A reference to an asset in the asset manager. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class AssetReference implements AssetInterface |
||||
{ |
||||
private $am; |
||||
private $name; |
||||
private $filters = array(); |
||||
|
||||
public function __construct(AssetManager $am, $name) |
||||
{ |
||||
$this->am = $am; |
||||
$this->name = $name; |
||||
} |
||||
|
||||
public function ensureFilter(FilterInterface $filter) |
||||
{ |
||||
$this->filters[] = $filter; |
||||
} |
||||
|
||||
public function getFilters() |
||||
{ |
||||
$this->flushFilters(); |
||||
|
||||
return $this->callAsset(__FUNCTION__); |
||||
} |
||||
|
||||
public function clearFilters() |
||||
{ |
||||
$this->filters = array(); |
||||
$this->callAsset(__FUNCTION__); |
||||
} |
||||
|
||||
public function load(FilterInterface $additionalFilter = null) |
||||
{ |
||||
$this->flushFilters(); |
||||
|
||||
return $this->callAsset(__FUNCTION__, array($additionalFilter)); |
||||
} |
||||
|
||||
public function dump(FilterInterface $additionalFilter = null) |
||||
{ |
||||
$this->flushFilters(); |
||||
|
||||
return $this->callAsset(__FUNCTION__, array($additionalFilter)); |
||||
} |
||||
|
||||
public function getContent() |
||||
{ |
||||
return $this->callAsset(__FUNCTION__); |
||||
} |
||||
|
||||
public function setContent($content) |
||||
{ |
||||
$this->callAsset(__FUNCTION__, array($content)); |
||||
} |
||||
|
||||
public function getSourceRoot() |
||||
{ |
||||
return $this->callAsset(__FUNCTION__); |
||||
} |
||||
|
||||
public function getSourcePath() |
||||
{ |
||||
return $this->callAsset(__FUNCTION__); |
||||
} |
||||
|
||||
public function getTargetPath() |
||||
{ |
||||
return $this->callAsset(__FUNCTION__); |
||||
} |
||||
|
||||
public function setTargetPath($targetPath) |
||||
{ |
||||
$this->callAsset(__FUNCTION__, array($targetPath)); |
||||
} |
||||
|
||||
public function getLastModified() |
||||
{ |
||||
return $this->callAsset(__FUNCTION__); |
||||
} |
||||
|
||||
// private |
||||
|
||||
private function callAsset($method, $arguments = array()) |
||||
{ |
||||
$asset = $this->am->get($this->name); |
||||
|
||||
return call_user_func_array(array($asset, $method), $arguments); |
||||
} |
||||
|
||||
private function flushFilters() |
||||
{ |
||||
$asset = $this->am->get($this->name); |
||||
|
||||
while ($filter = array_shift($this->filters)) { |
||||
$asset->ensureFilter($filter); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,135 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Asset; |
||||
|
||||
use Assetic\Filter\FilterCollection; |
||||
use Assetic\Filter\FilterInterface; |
||||
|
||||
/** |
||||
* A base abstract asset. |
||||
* |
||||
* The methods load() and getLastModified() are left undefined, although a |
||||
* reusable doLoad() method is available to child classes. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
abstract class BaseAsset implements AssetInterface |
||||
{ |
||||
private $filters; |
||||
private $sourceRoot; |
||||
private $sourcePath; |
||||
private $targetPath; |
||||
private $content; |
||||
private $loaded; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param array $filters Filters for the asset |
||||
*/ |
||||
public function __construct($filters = array(), $sourceRoot = null, $sourcePath = null) |
||||
{ |
||||
$this->filters = new FilterCollection($filters); |
||||
$this->sourceRoot = $sourceRoot; |
||||
$this->sourcePath = $sourcePath; |
||||
$this->loaded = false; |
||||
} |
||||
|
||||
public function __clone() |
||||
{ |
||||
$this->filters = clone $this->filters; |
||||
} |
||||
|
||||
public function ensureFilter(FilterInterface $filter) |
||||
{ |
||||
$this->filters->ensure($filter); |
||||
} |
||||
|
||||
public function getFilters() |
||||
{ |
||||
return $this->filters->all(); |
||||
} |
||||
|
||||
public function clearFilters() |
||||
{ |
||||
$this->filters->clear(); |
||||
} |
||||
|
||||
/** |
||||
* Encapsulates asset loading logic. |
||||
* |
||||
* @param string $content The asset content |
||||
* @param FilterInterface $additionalFilter An additional filter |
||||
*/ |
||||
protected function doLoad($content, FilterInterface $additionalFilter = null) |
||||
{ |
||||
$filter = clone $this->filters; |
||||
if ($additionalFilter) { |
||||
$filter->ensure($additionalFilter); |
||||
} |
||||
|
||||
$asset = clone $this; |
||||
$asset->setContent($content); |
||||
|
||||
$filter->filterLoad($asset); |
||||
$this->content = $asset->getContent(); |
||||
|
||||
$this->loaded = true; |
||||
} |
||||
|
||||
public function dump(FilterInterface $additionalFilter = null) |
||||
{ |
||||
if (!$this->loaded) { |
||||
$this->load(); |
||||
} |
||||
|
||||
$filter = clone $this->filters; |
||||
if ($additionalFilter) { |
||||
$filter->ensure($additionalFilter); |
||||
} |
||||
|
||||
$asset = clone $this; |
||||
$filter->filterDump($asset); |
||||
|
||||
return $asset->getContent(); |
||||
} |
||||
|
||||
public function getContent() |
||||
{ |
||||
return $this->content; |
||||
} |
||||
|
||||
public function setContent($content) |
||||
{ |
||||
$this->content = $content; |
||||
} |
||||
|
||||
public function getSourceRoot() |
||||
{ |
||||
return $this->sourceRoot; |
||||
} |
||||
|
||||
public function getSourcePath() |
||||
{ |
||||
return $this->sourcePath; |
||||
} |
||||
|
||||
public function getTargetPath() |
||||
{ |
||||
return $this->targetPath; |
||||
} |
||||
|
||||
public function setTargetPath($targetPath) |
||||
{ |
||||
$this->targetPath = $targetPath; |
||||
} |
||||
} |
||||
@ -0,0 +1,64 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Asset; |
||||
|
||||
use Assetic\Filter\FilterInterface; |
||||
|
||||
/** |
||||
* Represents an asset loaded from a file. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class FileAsset extends BaseAsset |
||||
{ |
||||
private $source; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param string $source An absolute path |
||||
* @param array $filters An array of filters |
||||
* @param string $sourceRoot The source asset root directory |
||||
* @param string $sourcePath The source asset path |
||||
* |
||||
* @throws InvalidArgumentException If the supplied root doesn't match the source when guessing the path |
||||
*/ |
||||
public function __construct($source, $filters = array(), $sourceRoot = null, $sourcePath = null) |
||||
{ |
||||
if (null === $sourceRoot) { |
||||
$sourceRoot = dirname($source); |
||||
if (null === $sourcePath) { |
||||
$sourcePath = basename($source); |
||||
} |
||||
} elseif (null === $sourcePath) { |
||||
if (0 !== strpos($source, $sourceRoot)) { |
||||
throw new \InvalidArgumentException(sprintf('The source "%s" is not in the root directory "%s"', $source, $sourceRoot)); |
||||
} |
||||
|
||||
$sourcePath = substr($source, strlen($sourceRoot) + 1); |
||||
} |
||||
|
||||
$this->source = $source; |
||||
|
||||
parent::__construct($filters, $sourceRoot, $sourcePath); |
||||
} |
||||
|
||||
public function load(FilterInterface $additionalFilter = null) |
||||
{ |
||||
$this->doLoad(file_get_contents($this->source), $additionalFilter); |
||||
} |
||||
|
||||
public function getLastModified() |
||||
{ |
||||
return filemtime($this->source); |
||||
} |
||||
} |
||||
@ -0,0 +1,101 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Asset; |
||||
|
||||
use Assetic\Filter\FilterInterface; |
||||
|
||||
/** |
||||
* A collection of assets loaded by glob. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class GlobAsset extends AssetCollection |
||||
{ |
||||
private $globs; |
||||
private $initialized; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param string|array $globs A single glob path or array of paths |
||||
* @param array $filters An array of filters |
||||
* @param string $root The root directory |
||||
*/ |
||||
public function __construct($globs, $filters = array(), $root = null) |
||||
{ |
||||
$this->globs = (array) $globs; |
||||
$this->initialized = false; |
||||
|
||||
parent::__construct(array(), $filters, $root); |
||||
} |
||||
|
||||
public function all() |
||||
{ |
||||
if (!$this->initialized) { |
||||
$this->initialize(); |
||||
} |
||||
|
||||
return parent::all(); |
||||
} |
||||
|
||||
public function load(FilterInterface $additionalFilter = null) |
||||
{ |
||||
if (!$this->initialized) { |
||||
$this->initialize(); |
||||
} |
||||
|
||||
parent::load($additionalFilter); |
||||
} |
||||
|
||||
public function dump(FilterInterface $additionalFilter = null) |
||||
{ |
||||
if (!$this->initialized) { |
||||
$this->initialize(); |
||||
} |
||||
|
||||
return parent::dump($additionalFilter); |
||||
} |
||||
|
||||
public function getLastModified() |
||||
{ |
||||
if (!$this->initialized) { |
||||
$this->initialize(); |
||||
} |
||||
|
||||
return parent::getLastModified(); |
||||
} |
||||
|
||||
public function getIterator() |
||||
{ |
||||
if (!$this->initialized) { |
||||
$this->initialize(); |
||||
} |
||||
|
||||
return parent::getIterator(); |
||||
} |
||||
|
||||
/** |
||||
* Initializes the collection based on the glob(s) passed in. |
||||
*/ |
||||
private function initialize() |
||||
{ |
||||
foreach ($this->globs as $glob) { |
||||
if (false !== $paths = glob($glob)) { |
||||
foreach ($paths as $path) { |
||||
$this->add(new FileAsset($path, array(), $this->getSourceRoot())); |
||||
} |
||||
} |
||||
} |
||||
|
||||
$this->initialized = true; |
||||
} |
||||
} |
||||
@ -0,0 +1,76 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Asset; |
||||
|
||||
use Assetic\Filter\FilterInterface; |
||||
|
||||
/** |
||||
* Represents an asset loaded via an HTTP request. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class HttpAsset extends BaseAsset |
||||
{ |
||||
private $sourceUrl; |
||||
private $ignoreErrors; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param string $sourceUrl The source URL |
||||
* @param array $filters An array of filters |
||||
* |
||||
* @throws InvalidArgumentException If the first argument is not an URL |
||||
*/ |
||||
public function __construct($sourceUrl, $filters = array(), $ignoreErrors = false) |
||||
{ |
||||
if (0 === strpos($sourceUrl, '//')) { |
||||
$sourceUrl = 'http:'.$sourceUrl; |
||||
} elseif (false === strpos($sourceUrl, '://')) { |
||||
throw new \InvalidArgumentException(sprintf('"%s" is not a valid URL.', $sourceUrl)); |
||||
} |
||||
|
||||
$this->sourceUrl = $sourceUrl; |
||||
$this->ignoreErrors = $ignoreErrors; |
||||
|
||||
list($scheme, $url) = explode('://', $sourceUrl, 2); |
||||
list($host, $path) = explode('/', $url, 2); |
||||
|
||||
parent::__construct($filters, $scheme.'://'.$host, $path); |
||||
} |
||||
|
||||
public function load(FilterInterface $additionalFilter = null) |
||||
{ |
||||
if (false === $content = @file_get_contents($this->sourceUrl)) { |
||||
if ($this->ignoreErrors) { |
||||
return; |
||||
} else { |
||||
throw new \RuntimeException(sprintf('Unable to load asset from URL "%s"', $this->sourceUrl)); |
||||
} |
||||
} |
||||
|
||||
$this->doLoad($content, $additionalFilter); |
||||
} |
||||
|
||||
public function getLastModified() |
||||
{ |
||||
if (false !== @file_get_contents($this->sourceUrl, false, stream_context_create(array('http' => array('method' => 'HEAD'))))) { |
||||
foreach ($http_response_header as $header) { |
||||
if (0 === stripos($header, 'Last-Modified: ')) { |
||||
list(, $mtime) = explode(':', $header, 2); |
||||
|
||||
return strtotime(trim($mtime)); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,55 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Asset; |
||||
|
||||
use Assetic\Filter\FilterInterface; |
||||
|
||||
/** |
||||
* Represents a string asset. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class StringAsset extends BaseAsset |
||||
{ |
||||
private $content; |
||||
private $lastModified; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param string $content The content of the asset |
||||
* @param array $filters Filters for the asset |
||||
* @param string $sourceRoot The source asset root directory |
||||
* @param string $sourcePath The source asset path |
||||
*/ |
||||
public function __construct($content, $filters = array(), $sourceRoot = null, $sourcePath = null) |
||||
{ |
||||
$this->content = $content; |
||||
|
||||
parent::__construct($filters, $sourceRoot, $sourcePath); |
||||
} |
||||
|
||||
public function load(FilterInterface $additionalFilter = null) |
||||
{ |
||||
$this->doLoad($this->content, $additionalFilter); |
||||
} |
||||
|
||||
public function setLastModified($lastModified) |
||||
{ |
||||
$this->lastModified = $lastModified; |
||||
} |
||||
|
||||
public function getLastModified() |
||||
{ |
||||
return $this->lastModified; |
||||
} |
||||
} |
||||
@ -0,0 +1,79 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
|
||||
/** |
||||
* Manages assets. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class AssetManager |
||||
{ |
||||
private $assets = array(); |
||||
|
||||
/** |
||||
* Gets an asset by name. |
||||
* |
||||
* @param string $name The asset name |
||||
* |
||||
* @return AssetInterface The asset |
||||
* |
||||
* @throws InvalidArgumentException If there is no asset by that name |
||||
*/ |
||||
public function get($name) |
||||
{ |
||||
if (!isset($this->assets[$name])) { |
||||
throw new \InvalidArgumentException(sprintf('There is no "%s" asset.', $name)); |
||||
} |
||||
|
||||
return $this->assets[$name]; |
||||
} |
||||
|
||||
/** |
||||
* Checks if the current asset manager has a certain asset. |
||||
* |
||||
* @param string $name an asset name |
||||
* |
||||
* @return Boolean True if the asset has been set, false if not |
||||
*/ |
||||
public function has($name) |
||||
{ |
||||
return isset($this->assets[$name]); |
||||
} |
||||
|
||||
/** |
||||
* Registers an asset to the current asset manager. |
||||
* |
||||
* @param string $name The asset name |
||||
* @param AssetInterface $asset The asset |
||||
*/ |
||||
public function set($name, AssetInterface $asset) |
||||
{ |
||||
if (!ctype_alnum(str_replace('_', '', $name))) { |
||||
throw new \InvalidArgumentException(sprintf('The name "%s" is invalid.', $name)); |
||||
} |
||||
|
||||
$this->assets[$name] = $asset; |
||||
} |
||||
|
||||
/** |
||||
* Returns an array of asset names. |
||||
* |
||||
* @return array An array of asset names |
||||
*/ |
||||
public function getNames() |
||||
{ |
||||
return array_keys($this->assets); |
||||
} |
||||
} |
||||
@ -0,0 +1,57 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
|
||||
/** |
||||
* Writes assets to the filesystem. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class AssetWriter |
||||
{ |
||||
private $dir; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param string $dir The base web directory |
||||
*/ |
||||
public function __construct($dir) |
||||
{ |
||||
$this->dir = $dir; |
||||
} |
||||
|
||||
public function writeManagerAssets(AssetManager $am) |
||||
{ |
||||
foreach ($am->getNames() as $name) { |
||||
$this->writeAsset($am->get($name)); |
||||
} |
||||
} |
||||
|
||||
public function writeAsset(AssetInterface $asset) |
||||
{ |
||||
static::write($this->dir . '/' . $asset->getTargetPath(), $asset->dump()); |
||||
} |
||||
|
||||
protected static function write($path, $contents) |
||||
{ |
||||
if (!is_dir($dir = dirname($path)) && false === @mkdir($dir, 0777, true)) { |
||||
throw new \RuntimeException('Unable to create directory '.$dir); |
||||
} |
||||
|
||||
if (false === @file_put_contents($path, $contents)) { |
||||
throw new \RuntimeException('Unable to write file '.$path); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,53 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Cache; |
||||
|
||||
/** |
||||
* Interface for a cache backend. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
interface CacheInterface |
||||
{ |
||||
/** |
||||
* Checks if the cache has a value for a key. |
||||
* |
||||
* @param string $key A unique key |
||||
* |
||||
* @return Boolean Whether the cache has a value for this key |
||||
*/ |
||||
public function has($key); |
||||
|
||||
/** |
||||
* Returns the value for a key. |
||||
* |
||||
* @param string $key A unique key |
||||
* |
||||
* @return string|null The value in the cache |
||||
*/ |
||||
public function get($key); |
||||
|
||||
/** |
||||
* Sets a value in the cache. |
||||
* |
||||
* @param string $key A unique key |
||||
* @param string $value The value to cache |
||||
*/ |
||||
public function set($key, $value); |
||||
|
||||
/** |
||||
* Removes a value from the cache. |
||||
* |
||||
* @param string $key A unique key |
||||
*/ |
||||
public function remove($key); |
||||
} |
||||
@ -0,0 +1,123 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Cache; |
||||
|
||||
/** |
||||
* A config cache stores values using var_export() and include. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class ConfigCache |
||||
{ |
||||
private $dir; |
||||
|
||||
/** |
||||
* Construct. |
||||
* |
||||
* @param string $dir The cache directory |
||||
*/ |
||||
public function __construct($dir) |
||||
{ |
||||
$this->dir = $dir; |
||||
} |
||||
|
||||
/** |
||||
* Checks of the cache has a file. |
||||
* |
||||
* @param string $resource A cache key |
||||
* |
||||
* @return Boolean True if a file exists |
||||
*/ |
||||
public function has($resource) |
||||
{ |
||||
return file_exists($this->getSourcePath($resource)); |
||||
} |
||||
|
||||
/** |
||||
* Writes a value to a file. |
||||
* |
||||
* @param string $resource A cache key |
||||
* @param mixed $value A value to cache |
||||
*/ |
||||
public function set($resource, $value) |
||||
{ |
||||
$path = $this->getSourcePath($resource); |
||||
|
||||
if (!is_dir($dir = dirname($path)) && false === @mkdir($dir, 0777, true)) { |
||||
// @codeCoverageIgnoreStart |
||||
throw new \RuntimeException('Unable to create directory '.$dir); |
||||
// @codeCoverageIgnoreEnd |
||||
} |
||||
|
||||
if (false === @file_put_contents($path, sprintf("<?php\n\n// $resource\nreturn %s;\n", var_export($value, true)))) {
|
||||
// @codeCoverageIgnoreStart |
||||
throw new \RuntimeException('Unable to write file '.$path); |
||||
// @codeCoverageIgnoreEnd |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Loads and returns the value for the supplied cache key. |
||||
* |
||||
* @param string $resource A cache key |
||||
* |
||||
* @return mixed The cached value |
||||
*/ |
||||
public function get($resource) |
||||
{ |
||||
$path = $this->getSourcePath($resource); |
||||
|
||||
if (!file_exists($path)) { |
||||
throw new \RuntimeException('There is no cached value for '.$resource); |
||||
} |
||||
|
||||
return include $path; |
||||
} |
||||
|
||||
/** |
||||
* Returns a timestamp for when the cache was created. |
||||
* |
||||
* @param string $resource A cache key |
||||
* |
||||
* @return integer A UNIX timestamp |
||||
*/ |
||||
public function getTimestamp($resource) |
||||
{ |
||||
$path = $this->getSourcePath($resource); |
||||
|
||||
if (!file_exists($path)) { |
||||
throw new \RuntimeException('There is no cached value for '.$resource); |
||||
} |
||||
|
||||
if (false === $mtime = @filemtime($path)) { |
||||
// @codeCoverageIgnoreStart |
||||
throw new \RuntimeException('Unable to determine file mtime for '.$path); |
||||
// @codeCoverageIgnoreEnd |
||||
} |
||||
|
||||
return $mtime; |
||||
} |
||||
|
||||
/** |
||||
* Returns the path where the file corresponding to the supplied cache key can be included from. |
||||
* |
||||
* @param string $resource A cache key |
||||
* |
||||
* @return string A file path |
||||
*/ |
||||
private function getSourcePath($resource) |
||||
{ |
||||
$key = md5($resource); |
||||
|
||||
return $this->dir.'/'.$key[0].'/'.$key.'.php'; |
||||
} |
||||
} |
||||
@ -0,0 +1,60 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Cache; |
||||
|
||||
/** |
||||
* Adds expiration to a cache backend. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class ExpiringCache implements CacheInterface |
||||
{ |
||||
private $cache; |
||||
private $lifetime; |
||||
|
||||
public function __construct(CacheInterface $cache, $lifetime) |
||||
{ |
||||
$this->cache = $cache; |
||||
$this->lifetime = $lifetime; |
||||
} |
||||
|
||||
public function has($key) |
||||
{ |
||||
if ($this->cache->has($key)) { |
||||
if (time() < $this->cache->get($key.'.expires')) { |
||||
return true; |
||||
} |
||||
|
||||
$this->cache->remove($key.'.expires'); |
||||
$this->cache->remove($key); |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
public function get($key) |
||||
{ |
||||
return $this->cache->get($key); |
||||
} |
||||
|
||||
public function set($key, $value) |
||||
{ |
||||
$this->cache->set($key.'.expires', time() + $this->lifetime); |
||||
$this->cache->set($key, $value); |
||||
} |
||||
|
||||
public function remove($key) |
||||
{ |
||||
$this->cache->remove($key.'.expires'); |
||||
$this->cache->remove($key); |
||||
} |
||||
} |
||||
@ -0,0 +1,65 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Cache; |
||||
|
||||
/** |
||||
* A simple filesystem cache. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class FilesystemCache implements CacheInterface |
||||
{ |
||||
private $dir; |
||||
|
||||
public function __construct($dir) |
||||
{ |
||||
$this->dir = $dir; |
||||
} |
||||
|
||||
public function has($key) |
||||
{ |
||||
return file_exists($this->dir.'/'.$key); |
||||
} |
||||
|
||||
public function get($key) |
||||
{ |
||||
$path = $this->dir.'/'.$key; |
||||
|
||||
if (!file_exists($path)) { |
||||
throw new \RuntimeException('There is no cached value for '.$key); |
||||
} |
||||
|
||||
return file_get_contents($path); |
||||
} |
||||
|
||||
public function set($key, $value) |
||||
{ |
||||
if (!is_dir($this->dir) && false === @mkdir($this->dir, 0777, true)) { |
||||
throw new \RuntimeException('Unable to create directory '.$this->dir); |
||||
} |
||||
|
||||
$path = $this->dir.'/'.$key; |
||||
|
||||
if (false === @file_put_contents($path, $value)) { |
||||
throw new \RuntimeException('Unable to write file '.$path); |
||||
} |
||||
} |
||||
|
||||
public function remove($key) |
||||
{ |
||||
$path = $this->dir.'/'.$key; |
||||
|
||||
if (file_exists($path) && false === @unlink($path)) { |
||||
throw new \RuntimeException('Unable to remove file '.$path); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,70 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Extension\Twig; |
||||
|
||||
use Assetic\Factory\AssetFactory; |
||||
|
||||
class AsseticExtension extends \Twig_Extension |
||||
{ |
||||
protected $factory; |
||||
protected $functions; |
||||
|
||||
public function __construct(AssetFactory $factory, $functions = array()) |
||||
{ |
||||
$this->factory = $factory; |
||||
$this->functions = array(); |
||||
|
||||
foreach ($functions as $function => $options) { |
||||
if (is_integer($function) && is_string($options)) { |
||||
$this->functions[$options] = array('filter' => $options); |
||||
} else { |
||||
$this->functions[$function] = $options + array('filter' => $function); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public function getTokenParsers() |
||||
{ |
||||
return array( |
||||
new AsseticTokenParser($this->factory, 'javascripts', 'js/*.js'), |
||||
new AsseticTokenParser($this->factory, 'stylesheets', 'css/*.css'), |
||||
new AsseticTokenParser($this->factory, 'image', 'images/*', true), |
||||
); |
||||
} |
||||
|
||||
public function getFunctions() |
||||
{ |
||||
$functions = array(); |
||||
foreach ($this->functions as $function => $filter) { |
||||
$functions[$function] = new AsseticFilterFunction($function); |
||||
} |
||||
|
||||
return $functions; |
||||
} |
||||
|
||||
public function getGlobals() |
||||
{ |
||||
return array( |
||||
'assetic' => array('debug' => $this->factory->isDebug()), |
||||
); |
||||
} |
||||
|
||||
public function getFilterInvoker($function) |
||||
{ |
||||
return new AsseticFilterInvoker($this->factory, $this->functions[$function]); |
||||
} |
||||
|
||||
public function getName() |
||||
{ |
||||
return 'assetic'; |
||||
} |
||||
} |
||||
@ -0,0 +1,29 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Extension\Twig; |
||||
|
||||
class AsseticFilterFunction extends \Twig_Function |
||||
{ |
||||
private $filter; |
||||
|
||||
public function __construct($filter, $options = array()) |
||||
{ |
||||
$this->filter = $filter; |
||||
|
||||
parent::__construct($options); |
||||
} |
||||
|
||||
public function compile() |
||||
{ |
||||
return sprintf('$this->env->getExtension(\'assetic\')->getFilterInvoker(\'%s\')->invoke', $this->filter); |
||||
} |
||||
} |
||||
@ -0,0 +1,59 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Extension\Twig; |
||||
|
||||
/** |
||||
* Filters a single asset. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class AsseticFilterInvoker |
||||
{ |
||||
private $factory; |
||||
private $filters; |
||||
private $options; |
||||
|
||||
public function __construct($factory, $filter) |
||||
{ |
||||
$this->factory = $factory; |
||||
|
||||
if (is_array($filter) && isset($filter['filter'])) { |
||||
$this->filters = (array) $filter['filter']; |
||||
$this->options = isset($filter['options']) ? (array) $filter['options'] : array(); |
||||
} else { |
||||
$this->filters = (array) $filter; |
||||
$this->options = array(); |
||||
} |
||||
} |
||||
|
||||
public function getFactory() |
||||
{ |
||||
return $this->factory; |
||||
} |
||||
|
||||
public function getFilters() |
||||
{ |
||||
return $this->filters; |
||||
} |
||||
|
||||
public function getOptions() |
||||
{ |
||||
return $this->options; |
||||
} |
||||
|
||||
public function invoke($input, array $options = array()) |
||||
{ |
||||
$asset = $this->factory->createAsset($input, $this->filters, $options + $this->options); |
||||
|
||||
return $asset->getTargetPath(); |
||||
} |
||||
} |
||||
@ -0,0 +1,123 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Extension\Twig; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
|
||||
class AsseticNode extends \Twig_Node |
||||
{ |
||||
/** |
||||
* Constructor. |
||||
* |
||||
* Available attributes: |
||||
* |
||||
* * debug: The debug mode |
||||
* * combine: Whether to combine assets |
||||
* * var_name: The name of the variable to expose to the body node |
||||
* |
||||
* @param AssetInterface $asset The asset |
||||
* @param Twig_NodeInterface $body The body node |
||||
* @param array $inputs An array of input strings |
||||
* @param array $filters An array of filter strings |
||||
* @param string $name The name of the asset |
||||
* @param array $attributes An array of attributes |
||||
* @param integer $lineno The line number |
||||
* @param string $tag The tag name |
||||
*/ |
||||
public function __construct(AssetInterface $asset, \Twig_NodeInterface $body, array $inputs, array $filters, $name, array $attributes = array(), $lineno = 0, $tag = null) |
||||
{ |
||||
$nodes = array('body' => $body); |
||||
|
||||
$attributes = array_replace( |
||||
array('debug' => null, 'combine' => null, 'var_name' => 'asset_url'), |
||||
$attributes, |
||||
array('asset' => $asset, 'inputs' => $inputs, 'filters' => $filters, 'name' => $name) |
||||
); |
||||
|
||||
parent::__construct($nodes, $attributes, $lineno, $tag); |
||||
} |
||||
|
||||
public function compile(\Twig_Compiler $compiler) |
||||
{ |
||||
$compiler->addDebugInfo($this); |
||||
|
||||
$combine = $this->getAttribute('combine'); |
||||
$debug = $this->getAttribute('debug'); |
||||
|
||||
if (null === $combine && null !== $debug) { |
||||
$combine = !$debug; |
||||
} |
||||
|
||||
if (null === $combine) { |
||||
$compiler |
||||
->write("if (isset(\$context['assetic']['debug']) && \$context['assetic']['debug']) {\n") |
||||
->indent() |
||||
; |
||||
|
||||
$this->compileDebug($compiler); |
||||
|
||||
$compiler |
||||
->outdent() |
||||
->write("} else {\n") |
||||
->indent() |
||||
; |
||||
|
||||
$this->compileAsset($compiler, $this->getAttribute('asset'), $this->getAttribute('name')); |
||||
|
||||
$compiler |
||||
->outdent() |
||||
->write("}\n") |
||||
; |
||||
} elseif ($combine) { |
||||
$this->compileAsset($compiler, $this->getAttribute('asset'), $this->getAttribute('name')); |
||||
} else { |
||||
$this->compileDebug($compiler); |
||||
} |
||||
|
||||
$compiler |
||||
->write('unset($context[') |
||||
->repr($this->getAttribute('var_name')) |
||||
->raw("]);\n") |
||||
; |
||||
} |
||||
|
||||
protected function compileDebug(\Twig_Compiler $compiler) |
||||
{ |
||||
$i = 0; |
||||
foreach ($this->getAttribute('asset') as $leaf) { |
||||
$leafName = $this->getAttribute('name').'_'.$i++; |
||||
$this->compileAsset($compiler, $leaf, $leafName); |
||||
} |
||||
} |
||||
|
||||
protected function compileAsset(\Twig_Compiler $compiler, AssetInterface $asset, $name) |
||||
{ |
||||
$compiler |
||||
->write("// asset \"$name\"\n") |
||||
->write('$context[') |
||||
->repr($this->getAttribute('var_name')) |
||||
->raw('] = ') |
||||
; |
||||
|
||||
$this->compileAssetUrl($compiler, $asset, $name); |
||||
|
||||
$compiler |
||||
->raw(";\n") |
||||
->subcompile($this->getNode('body')) |
||||
; |
||||
} |
||||
|
||||
protected function compileAssetUrl(\Twig_Compiler $compiler, AssetInterface $asset, $name) |
||||
{ |
||||
$compiler->repr($asset->getTargetPath()); |
||||
} |
||||
} |
||||
@ -0,0 +1,135 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Extension\Twig; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
use Assetic\Factory\AssetFactory; |
||||
|
||||
class AsseticTokenParser extends \Twig_TokenParser |
||||
{ |
||||
private $factory; |
||||
private $tag; |
||||
private $output; |
||||
private $single; |
||||
private $extensions; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* Attributes can be added to the tag by passing names as the options |
||||
* array. These values, if found, will be passed to the factory and node. |
||||
* |
||||
* @param AssetFactory $factory The asset factory |
||||
* @param string $tag The tag name |
||||
* @param string $output The default output string |
||||
* @param Boolean $single Whether to force a single asset |
||||
* @param array $extensions Additional attribute names to look for |
||||
*/ |
||||
public function __construct(AssetFactory $factory, $tag, $output, $single = false, array $extensions = array()) |
||||
{ |
||||
$this->factory = $factory; |
||||
$this->tag = $tag; |
||||
$this->output = $output; |
||||
$this->single = $single; |
||||
$this->extensions = $extensions; |
||||
} |
||||
|
||||
public function parse(\Twig_Token $token) |
||||
{ |
||||
$inputs = array(); |
||||
$filters = array(); |
||||
$name = null; |
||||
$attributes = array( |
||||
'output' => $this->output, |
||||
'var_name' => 'asset_url', |
||||
); |
||||
|
||||
$stream = $this->parser->getStream(); |
||||
while (!$stream->test(\Twig_Token::BLOCK_END_TYPE)) { |
||||
if ($stream->test(\Twig_Token::STRING_TYPE)) { |
||||
// '@jquery', 'js/src/core/*', 'js/src/extra.js' |
||||
$inputs[] = $stream->next()->getValue(); |
||||
} elseif ($stream->test(\Twig_Token::NAME_TYPE, 'filter')) { |
||||
// filter='yui_js' |
||||
$stream->next(); |
||||
$stream->expect(\Twig_Token::OPERATOR_TYPE, '='); |
||||
$filters = array_merge($filters, array_filter(array_map('trim', explode(',', $stream->expect(\Twig_Token::STRING_TYPE)->getValue())))); |
||||
} elseif ($stream->test(\Twig_Token::NAME_TYPE, 'output')) { |
||||
// output='js/packed/*.js' OR output='js/core.js' |
||||
$stream->next(); |
||||
$stream->expect(\Twig_Token::OPERATOR_TYPE, '='); |
||||
$attributes['output'] = $stream->expect(\Twig_Token::STRING_TYPE)->getValue(); |
||||
} elseif ($stream->test(\Twig_Token::NAME_TYPE, 'name')) { |
||||
// name='core_js' |
||||
$stream->next(); |
||||
$stream->expect(\Twig_Token::OPERATOR_TYPE, '='); |
||||
$name = $stream->expect(\Twig_Token::STRING_TYPE)->getValue(); |
||||
} elseif ($stream->test(\Twig_Token::NAME_TYPE, 'as')) { |
||||
// as='the_url' |
||||
$stream->next(); |
||||
$stream->expect(\Twig_Token::OPERATOR_TYPE, '='); |
||||
$attributes['var_name'] = $stream->expect(\Twig_Token::STRING_TYPE)->getValue(); |
||||
} elseif ($stream->test(\Twig_Token::NAME_TYPE, 'debug')) { |
||||
// debug=true |
||||
$stream->next(); |
||||
$stream->expect(\Twig_Token::OPERATOR_TYPE, '='); |
||||
$attributes['debug'] = 'true' == $stream->expect(\Twig_Token::NAME_TYPE, array('true', 'false'))->getValue(); |
||||
} elseif ($stream->test(\Twig_Token::NAME_TYPE, 'combine')) { |
||||
// combine=true |
||||
$stream->next(); |
||||
$stream->expect(\Twig_Token::OPERATOR_TYPE, '='); |
||||
$attributes['combine'] = 'true' == $stream->expect(\Twig_Token::NAME_TYPE, array('true', 'false'))->getValue(); |
||||
} elseif ($stream->test(\Twig_Token::NAME_TYPE, $this->extensions)) { |
||||
// an arbitrary configured attribute |
||||
$key = $stream->next()->getValue(); |
||||
$stream->expect(\Twig_Token::OPERATOR_TYPE, '='); |
||||
$attributes[$key] = $stream->expect(\Twig_Token::STRING_TYPE)->getValue(); |
||||
} else { |
||||
$token = $stream->getCurrent(); |
||||
throw new \Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', \Twig_Token::typeToEnglish($token->getType(), $token->getLine()), $token->getValue()), $token->getLine()); |
||||
} |
||||
} |
||||
|
||||
$stream->expect(\Twig_Token::BLOCK_END_TYPE); |
||||
|
||||
$body = $this->parser->subparse(array($this, 'testEndTag'), true); |
||||
|
||||
$stream->expect(\Twig_Token::BLOCK_END_TYPE); |
||||
|
||||
if ($this->single && 1 < count($inputs)) { |
||||
$inputs = array_slice($inputs, -1); |
||||
} |
||||
|
||||
if (!$name) { |
||||
$name = $this->factory->generateAssetName($inputs, $filters, $attributes); |
||||
} |
||||
|
||||
$asset = $this->factory->createAsset($inputs, $filters, $attributes + array('name' => $name)); |
||||
|
||||
return $this->createNode($asset, $body, $inputs, $filters, $name, $attributes, $token->getLine(), $this->getTag()); |
||||
} |
||||
|
||||
public function getTag() |
||||
{ |
||||
return $this->tag; |
||||
} |
||||
|
||||
public function testEndTag(\Twig_Token $token) |
||||
{ |
||||
return $token->test(array('end'.$this->getTag())); |
||||
} |
||||
|
||||
protected function createNode(AssetInterface $asset, \Twig_NodeInterface $body, array $inputs, array $filters, $name, array $attributes = array(), $lineno = 0, $tag = null) |
||||
{ |
||||
return new AsseticNode($asset, $body, $inputs, $filters, $name, $attributes, $lineno, $tag); |
||||
} |
||||
} |
||||
@ -0,0 +1,96 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Extension\Twig; |
||||
|
||||
use Assetic\Factory\Loader\FormulaLoaderInterface; |
||||
use Assetic\Factory\Resource\ResourceInterface; |
||||
|
||||
/** |
||||
* Loads asset formulae from Twig templates. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class TwigFormulaLoader implements FormulaLoaderInterface |
||||
{ |
||||
private $twig; |
||||
|
||||
public function __construct(\Twig_Environment $twig) |
||||
{ |
||||
$this->twig = $twig; |
||||
} |
||||
|
||||
public function load(ResourceInterface $resource) |
||||
{ |
||||
try { |
||||
$tokens = $this->twig->tokenize($resource->getContent(), (string) $resource); |
||||
$nodes = $this->twig->parse($tokens); |
||||
} catch (\Exception $e) { |
||||
return array(); |
||||
} |
||||
|
||||
return $this->loadNode($nodes); |
||||
} |
||||
|
||||
/** |
||||
* Loads assets from the supplied node. |
||||
* |
||||
* @return array An array of asset formulae indexed by name |
||||
*/ |
||||
private function loadNode(\Twig_Node $node) |
||||
{ |
||||
$formulae = array(); |
||||
|
||||
if ($node instanceof AsseticNode) { |
||||
$formulae[$node->getAttribute('name')] = array( |
||||
$node->getAttribute('inputs'), |
||||
$node->getAttribute('filters'), |
||||
array( |
||||
'output' => $node->getAttribute('asset')->getTargetPath(), |
||||
'name' => $node->getAttribute('name'), |
||||
'debug' => $node->getAttribute('debug'), |
||||
'combine' => $node->getAttribute('combine'), |
||||
), |
||||
); |
||||
} elseif ($node instanceof \Twig_Node_Expression_Function) { |
||||
$name = version_compare(\Twig_Environment::VERSION, '1.2.0-DEV', '<') |
||||
? $node->getNode('name')->getAttribute('name') |
||||
: $node->getAttribute('name'); |
||||
|
||||
if ($this->twig->getFunction($name) instanceof AsseticFilterFunction) { |
||||
$arguments = array(); |
||||
foreach ($node->getNode('arguments') as $argument) { |
||||
$arguments[] = eval('return '.$this->twig->compile($argument).';'); |
||||
} |
||||
|
||||
$invoker = $this->twig->getExtension('assetic')->getFilterInvoker($name); |
||||
|
||||
$inputs = isset($arguments[0]) ? (array) $arguments[0] : array(); |
||||
$filters = $invoker->getFilters(); |
||||
$options = array_replace($invoker->getOptions(), isset($arguments[1]) ? $arguments[1] : array()); |
||||
|
||||
if (!isset($options['name'])) { |
||||
$options['name'] = $invoker->getFactory()->generateAssetName($inputs, $filters, $options); |
||||
} |
||||
|
||||
$formulae[$options['name']] = array($inputs, $filters, $options); |
||||
} |
||||
} |
||||
|
||||
foreach ($node as $child) { |
||||
if ($child instanceof \Twig_Node) { |
||||
$formulae += $this->loadNode($child); |
||||
} |
||||
} |
||||
|
||||
return $formulae; |
||||
} |
||||
} |
||||
@ -0,0 +1,54 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Extension\Twig; |
||||
|
||||
use Assetic\Factory\Resource\ResourceInterface; |
||||
|
||||
/** |
||||
* A Twig template resource. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class TwigResource implements ResourceInterface |
||||
{ |
||||
private $loader; |
||||
private $name; |
||||
|
||||
public function __construct(\Twig_LoaderInterface $loader, $name) |
||||
{ |
||||
$this->loader = $loader; |
||||
$this->name = $name; |
||||
} |
||||
|
||||
public function getContent() |
||||
{ |
||||
try { |
||||
return $this->loader->getSource($this->name); |
||||
} catch (\Twig_Error_Loader $e) { |
||||
return ''; |
||||
} |
||||
} |
||||
|
||||
public function isFresh($timestamp) |
||||
{ |
||||
try { |
||||
return $this->loader->isFresh($this->name, $timestamp); |
||||
} catch (\Twig_Error_Loader $e) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
public function __toString() |
||||
{ |
||||
return $this->name; |
||||
} |
||||
} |
||||
@ -0,0 +1,359 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Factory; |
||||
|
||||
use Assetic\Asset\AssetCollection; |
||||
use Assetic\Asset\AssetInterface; |
||||
use Assetic\Asset\AssetReference; |
||||
use Assetic\Asset\FileAsset; |
||||
use Assetic\Asset\GlobAsset; |
||||
use Assetic\Asset\HttpAsset; |
||||
use Assetic\AssetManager; |
||||
use Assetic\Factory\Worker\WorkerInterface; |
||||
use Assetic\FilterManager; |
||||
|
||||
/** |
||||
* The asset factory creates asset objects. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class AssetFactory |
||||
{ |
||||
private $root; |
||||
private $debug; |
||||
private $output; |
||||
private $workers; |
||||
private $am; |
||||
private $fm; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param string $root The default root directory |
||||
* @param string $output The default output string |
||||
* @param Boolean $debug Filters prefixed with a "?" will be omitted in debug mode |
||||
*/ |
||||
public function __construct($root, $debug = false) |
||||
{ |
||||
$this->root = rtrim($root, '/'); |
||||
$this->debug = $debug; |
||||
$this->output = 'assetic/*'; |
||||
$this->workers = array(); |
||||
} |
||||
|
||||
/** |
||||
* Sets debug mode for the current factory. |
||||
* |
||||
* @param Boolean $debug Debug mode |
||||
*/ |
||||
public function setDebug($debug) |
||||
{ |
||||
$this->debug = $debug; |
||||
} |
||||
|
||||
/** |
||||
* Checks if the factory is in debug mode. |
||||
* |
||||
* @return Boolean Debug mode |
||||
*/ |
||||
public function isDebug() |
||||
{ |
||||
return $this->debug; |
||||
} |
||||
|
||||
/** |
||||
* Sets the default output string. |
||||
* |
||||
* @param string $output The default output string |
||||
*/ |
||||
public function setDefaultOutput($output) |
||||
{ |
||||
$this->output = $output; |
||||
} |
||||
|
||||
/** |
||||
* Adds a factory worker. |
||||
* |
||||
* @param WorkerInterface $worker A worker |
||||
*/ |
||||
public function addWorker(WorkerInterface $worker) |
||||
{ |
||||
$this->workers[] = $worker; |
||||
} |
||||
|
||||
/** |
||||
* Returns the current asset manager. |
||||
* |
||||
* @return AssetManager|null The asset manager |
||||
*/ |
||||
public function getAssetManager() |
||||
{ |
||||
return $this->am; |
||||
} |
||||
|
||||
/** |
||||
* Sets the asset manager to use when creating asset references. |
||||
* |
||||
* @param AssetManager $am The asset manager |
||||
*/ |
||||
public function setAssetManager(AssetManager $am) |
||||
{ |
||||
$this->am = $am; |
||||
} |
||||
|
||||
/** |
||||
* Returns the current filter manager. |
||||
* |
||||
* @return FilterManager|null The filter manager |
||||
*/ |
||||
public function getFilterManager() |
||||
{ |
||||
return $this->fm; |
||||
} |
||||
|
||||
/** |
||||
* Sets the filter manager to use when adding filters. |
||||
* |
||||
* @param FilterManager $fm The filter manager |
||||
*/ |
||||
public function setFilterManager(FilterManager $fm) |
||||
{ |
||||
$this->fm = $fm; |
||||
} |
||||
|
||||
/** |
||||
* Creates a new asset. |
||||
* |
||||
* Prefixing a filter name with a question mark will cause it to be |
||||
* omitted when the factory is in debug mode. |
||||
* |
||||
* Available options: |
||||
* |
||||
* * output: An output string |
||||
* * name: An asset name for interpolation in output patterns |
||||
* * debug: Forces debug mode on or off for this asset |
||||
* * root: An array or string of more root directories |
||||
* |
||||
* @param array|string $inputs An array of input strings |
||||
* @param array|string $filters An array of filter names |
||||
* @param array $options An array of options |
||||
* |
||||
* @return AssetCollection An asset collection |
||||
*/ |
||||
public function createAsset($inputs = array(), $filters = array(), array $options = array()) |
||||
{ |
||||
if (!is_array($inputs)) { |
||||
$inputs = array($inputs); |
||||
} |
||||
|
||||
if (!is_array($filters)) { |
||||
$filters = array($filters); |
||||
} |
||||
|
||||
if (!isset($options['output'])) { |
||||
$options['output'] = $this->output; |
||||
} |
||||
|
||||
if (!isset($options['debug'])) { |
||||
$options['debug'] = $this->debug; |
||||
} |
||||
|
||||
if (!isset($options['root'])) { |
||||
$options['root'] = array($this->root); |
||||
} else { |
||||
if (!is_array($options['root'])) { |
||||
$options['root'] = array($options['root']); |
||||
} |
||||
|
||||
$options['root'][] = $this->root; |
||||
} |
||||
|
||||
if (!isset($options['name'])) { |
||||
$options['name'] = $this->generateAssetName($inputs, $filters, $options); |
||||
} |
||||
|
||||
$asset = $this->createAssetCollection(); |
||||
$extensions = array(); |
||||
|
||||
// inner assets |
||||
foreach ($inputs as $input) { |
||||
if (is_array($input)) { |
||||
// nested formula |
||||
$asset->add(call_user_func_array(array($this, 'createAsset'), $input)); |
||||
} else { |
||||
$asset->add($this->parseInput($input, $options)); |
||||
$extensions[pathinfo($input, PATHINFO_EXTENSION)] = true; |
||||
} |
||||
} |
||||
|
||||
// filters |
||||
foreach ($filters as $filter) { |
||||
if ('?' != $filter[0]) { |
||||
$asset->ensureFilter($this->getFilter($filter)); |
||||
} elseif (!$options['debug']) { |
||||
$asset->ensureFilter($this->getFilter(substr($filter, 1))); |
||||
} |
||||
} |
||||
|
||||
// append consensus extension if missing |
||||
if (1 == count($extensions) && !pathinfo($options['output'], PATHINFO_EXTENSION) && $extension = key($extensions)) { |
||||
$options['output'] .= '.'.$extension; |
||||
} |
||||
|
||||
// output --> target url |
||||
$asset->setTargetPath(str_replace('*', $options['name'], $options['output'])); |
||||
|
||||
// apply workers |
||||
$this->processAsset($asset); |
||||
|
||||
return $asset; |
||||
} |
||||
|
||||
public function generateAssetName($inputs, $filters, $options = array()) |
||||
{ |
||||
foreach (array_diff(array_keys($options), array('output', 'debug', 'root')) as $key) { |
||||
unset($options[$key]); |
||||
} |
||||
|
||||
ksort($options); |
||||
|
||||
return substr(sha1(serialize($inputs).serialize($filters).serialize($options)), 0, 7); |
||||
} |
||||
|
||||
/** |
||||
* Parses an input string string into an asset. |
||||
* |
||||
* The input string can be one of the following: |
||||
* |
||||
* * A reference: If the string starts with an "at" sign it will be interpreted as a reference to an asset in the asset manager |
||||
* * An absolute URL: If the string contains "://" or starts with "//" it will be interpreted as an HTTP asset |
||||
* * A glob: If the string contains a "*" it will be interpreted as a glob |
||||
* * A path: Otherwise the string is interpreted as a filesystem path |
||||
* |
||||
* Both globs and paths will be absolutized using the current root directory. |
||||
* |
||||
* @param string $input An input string |
||||
* @param array $options An array of options |
||||
* |
||||
* @return AssetInterface An asset |
||||
*/ |
||||
protected function parseInput($input, array $options = array()) |
||||
{ |
||||
if ('@' == $input[0]) { |
||||
return $this->createAssetReference(substr($input, 1)); |
||||
} |
||||
|
||||
if (false !== strpos($input, '://') || 0 === strpos($input, '//')) { |
||||
return $this->createHttpAsset($input); |
||||
} |
||||
|
||||
if (self::isAbsolutePath($input)) { |
||||
if ($root = self::findRootDir($input, $options['root'])) { |
||||
$path = ltrim(substr($input, strlen($root)), '/'); |
||||
} else { |
||||
$path = null; |
||||
} |
||||
} else { |
||||
$root = $this->root; |
||||
$path = $input; |
||||
$input = $this->root.'/'.$path; |
||||
} |
||||
if (false !== strpos($input, '*')) { |
||||
return $this->createGlobAsset($input, $root); |
||||
} else { |
||||
return $this->createFileAsset($input, $root, $path); |
||||
} |
||||
} |
||||
|
||||
protected function createAssetCollection() |
||||
{ |
||||
return new AssetCollection(); |
||||
} |
||||
|
||||
protected function createAssetReference($name) |
||||
{ |
||||
if (!$this->am) { |
||||
throw new \LogicException('There is no asset manager.'); |
||||
} |
||||
|
||||
return new AssetReference($this->am, $name); |
||||
} |
||||
|
||||
protected function createHttpAsset($sourceUrl) |
||||
{ |
||||
return new HttpAsset($sourceUrl); |
||||
} |
||||
|
||||
protected function createGlobAsset($glob, $root = null) |
||||
{ |
||||
return new GlobAsset($glob, array(), $root); |
||||
} |
||||
|
||||
protected function createFileAsset($source, $root = null, $path = null) |
||||
{ |
||||
return new FileAsset($source, array(), $root, $path); |
||||
} |
||||
|
||||
protected function getFilter($name) |
||||
{ |
||||
if (!$this->fm) { |
||||
throw new \LogicException('There is no filter manager.'); |
||||
} |
||||
|
||||
return $this->fm->get($name); |
||||
} |
||||
|
||||
/** |
||||
* Filters an asset through the factory workers. |
||||
* |
||||
* Each leaf asset will be processed first if the asset is traversable, |
||||
* followed by the asset itself. |
||||
* |
||||
* @param AssetInterface $asset An asset |
||||
*/ |
||||
private function processAsset(AssetInterface $asset) |
||||
{ |
||||
if ($asset instanceof \Traversable) { |
||||
foreach ($asset as $leaf) { |
||||
foreach ($this->workers as $worker) { |
||||
$worker->process($leaf); |
||||
} |
||||
} |
||||
} |
||||
|
||||
foreach ($this->workers as $worker) { |
||||
$worker->process($asset); |
||||
} |
||||
} |
||||
|
||||
private static function isAbsolutePath($path) |
||||
{ |
||||
return '/' == $path[0] || '\\' == $path[0] || (3 < strlen($path) && ctype_alpha($path[0]) && $path[1] == ':' && ('\\' == $path[2] || '/' == $path[2])); |
||||
} |
||||
|
||||
/** |
||||
* Loops through the root directories and returns the first match. |
||||
* |
||||
* @param string $path An absolute path |
||||
* @param array $roots An array of root directories |
||||
* |
||||
* @return string|null The matching root directory, if found |
||||
*/ |
||||
private static function findRootDir($path, array $roots) |
||||
{ |
||||
foreach ($roots as $root) { |
||||
if (0 === strpos($path, $root)) { |
||||
return $root; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,204 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Factory; |
||||
|
||||
use Assetic\AssetManager; |
||||
use Assetic\Factory\Loader\FormulaLoaderInterface; |
||||
use Assetic\Factory\Resource\ResourceInterface; |
||||
|
||||
/** |
||||
* A lazy asset manager is a composition of a factory and many formula loaders. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class LazyAssetManager extends AssetManager |
||||
{ |
||||
private $factory; |
||||
private $loaders; |
||||
private $resources; |
||||
private $formulae; |
||||
private $loaded; |
||||
private $loading; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param AssetFactory $factory The asset factory |
||||
* @param array $loaders An array of loaders indexed by alias |
||||
*/ |
||||
public function __construct(AssetFactory $factory, $loaders = array()) |
||||
{ |
||||
$this->factory = $factory; |
||||
$this->loaders = array(); |
||||
$this->resources = array(); |
||||
$this->formulae = array(); |
||||
$this->loaded = false; |
||||
$this->loading = false; |
||||
|
||||
foreach ($loaders as $alias => $loader) { |
||||
$this->setLoader($alias, $loader); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Adds a loader to the asset manager. |
||||
* |
||||
* @param string $alias An alias for the loader |
||||
* @param FormulaLoaderInterface $loader A loader |
||||
*/ |
||||
public function setLoader($alias, FormulaLoaderInterface $loader) |
||||
{ |
||||
$this->loaders[$alias] = $loader; |
||||
$this->loaded = false; |
||||
} |
||||
|
||||
/** |
||||
* Adds a resource to the asset manager. |
||||
* |
||||
* @param ResourceInterface $resource A resource |
||||
* @param string $loader The loader alias for this resource |
||||
*/ |
||||
public function addResource(ResourceInterface $resource, $loader) |
||||
{ |
||||
$this->resources[$loader][] = $resource; |
||||
$this->loaded = false; |
||||
} |
||||
|
||||
/** |
||||
* Returns an array of resources. |
||||
* |
||||
* @return array An array of resources |
||||
*/ |
||||
public function getResources() |
||||
{ |
||||
$resources = array(); |
||||
foreach ($this->resources as $r) { |
||||
$resources = array_merge($resources, $r); |
||||
} |
||||
|
||||
return $resources; |
||||
} |
||||
|
||||
/** |
||||
* Checks for an asset formula. |
||||
* |
||||
* @param string $name An asset name |
||||
* |
||||
* @return Boolean If there is a formula |
||||
*/ |
||||
public function hasFormula($name) |
||||
{ |
||||
if (!$this->loaded) { |
||||
$this->load(); |
||||
} |
||||
|
||||
return isset($this->formulae[$name]); |
||||
} |
||||
|
||||
/** |
||||
* Returns an asset's formula. |
||||
* |
||||
* @param string $name An asset name |
||||
* |
||||
* @return array The formula |
||||
* |
||||
* @throws InvalidArgumentException If there is no formula by that name |
||||
*/ |
||||
public function getFormula($name) |
||||
{ |
||||
if (!$this->loaded) { |
||||
$this->load(); |
||||
} |
||||
|
||||
if (!isset($this->formulae[$name])) { |
||||
throw new \InvalidArgumentException(sprintf('There is no "%s" formula.', $name)); |
||||
} |
||||
|
||||
return $this->formulae[$name]; |
||||
} |
||||
|
||||
/** |
||||
* Sets a formula on the asset manager. |
||||
* |
||||
* @param string $name An asset name |
||||
* @param array $formula A formula |
||||
*/ |
||||
public function setFormula($name, array $formula) |
||||
{ |
||||
$this->formulae[$name] = $formula; |
||||
} |
||||
|
||||
/** |
||||
* Loads formulae from resources. |
||||
* |
||||
* @throws LogicException If a resource has been added to an invalid loader |
||||
*/ |
||||
public function load() |
||||
{ |
||||
if ($this->loading) { |
||||
return; |
||||
} |
||||
|
||||
if ($diff = array_diff(array_keys($this->resources), array_keys($this->loaders))) { |
||||
throw new \LogicException('The following loader(s) are not registered: '.implode(', ', $diff)); |
||||
} |
||||
|
||||
$this->loading = true; |
||||
|
||||
foreach ($this->resources as $loader => $resources) { |
||||
foreach ($resources as $resource) { |
||||
$this->formulae = array_replace($this->formulae, $this->loaders[$loader]->load($resource)); |
||||
} |
||||
} |
||||
|
||||
$this->loaded = true; |
||||
$this->loading = false; |
||||
} |
||||
|
||||
public function get($name) |
||||
{ |
||||
if (!$this->loaded) { |
||||
$this->load(); |
||||
} |
||||
|
||||
if (!parent::has($name) && isset($this->formulae[$name])) { |
||||
list($inputs, $filters, $options) = $this->formulae[$name]; |
||||
$options['name'] = $name; |
||||
parent::set($name, $this->factory->createAsset($inputs, $filters, $options)); |
||||
} |
||||
|
||||
return parent::get($name); |
||||
} |
||||
|
||||
public function has($name) |
||||
{ |
||||
if (!$this->loaded) { |
||||
$this->load(); |
||||
} |
||||
|
||||
return isset($this->formulae[$name]) || parent::has($name); |
||||
} |
||||
|
||||
public function getNames() |
||||
{ |
||||
if (!$this->loaded) { |
||||
$this->load(); |
||||
} |
||||
|
||||
return array_unique(array_merge(parent::getNames(), array_keys($this->formulae))); |
||||
} |
||||
|
||||
public function isDebug() |
||||
{ |
||||
return $this->factory->isDebug(); |
||||
} |
||||
} |
||||
@ -0,0 +1,159 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Factory\Loader; |
||||
|
||||
use Assetic\Factory\AssetFactory; |
||||
use Assetic\Factory\Resource\ResourceInterface; |
||||
|
||||
/** |
||||
* Loads asset formulae from PHP files. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
abstract class BasePhpFormulaLoader implements FormulaLoaderInterface |
||||
{ |
||||
protected $factory; |
||||
protected $prototypes; |
||||
|
||||
public function __construct(AssetFactory $factory) |
||||
{ |
||||
$this->factory = $factory; |
||||
$this->prototypes = array(); |
||||
|
||||
foreach ($this->registerPrototypes() as $prototype => $options) { |
||||
$this->addPrototype($prototype, $options); |
||||
} |
||||
} |
||||
|
||||
public function addPrototype($prototype, array $options = array()) |
||||
{ |
||||
$tokens = token_get_all('<?php '.$prototype); |
||||
array_shift($tokens); |
||||
|
||||
$this->prototypes[$prototype] = array($tokens, $options); |
||||
} |
||||
|
||||
public function load(ResourceInterface $resource) |
||||
{ |
||||
if (!$nbProtos = count($this->prototypes)) { |
||||
throw new \LogicException('There are no prototypes registered.'); |
||||
} |
||||
|
||||
$buffers = array_fill(0, $nbProtos, ''); |
||||
$bufferLevels = array_fill(0, $nbProtos, 0); |
||||
$buffersInWildcard = array(); |
||||
|
||||
$tokens = token_get_all($resource->getContent()); |
||||
$calls = array(); |
||||
|
||||
while ($token = array_shift($tokens)) { |
||||
$current = self::tokenToString($token); |
||||
// loop through each prototype (by reference) |
||||
foreach (array_keys($this->prototypes) as $i) { |
||||
$prototype =& $this->prototypes[$i][0]; |
||||
$options = $this->prototypes[$i][1]; |
||||
$buffer =& $buffers[$i]; |
||||
$level =& $bufferLevels[$i]; |
||||
|
||||
if (isset($buffersInWildcard[$i])) { |
||||
switch ($current) { |
||||
case '(': ++$level; break; |
||||
case ')': --$level; break; |
||||
} |
||||
|
||||
$buffer .= $current; |
||||
|
||||
if (!$level) { |
||||
$calls[] = array($buffer.';', $options); |
||||
$buffer = ''; |
||||
unset($buffersInWildcard[$i]); |
||||
} |
||||
} elseif ($current == self::tokenToString(current($prototype))) { |
||||
$buffer .= $current; |
||||
if ('*' == self::tokenToString(next($prototype))) { |
||||
$buffersInWildcard[$i] = true; |
||||
++$level; |
||||
} |
||||
} else { |
||||
reset($prototype); |
||||
unset($buffersInWildcard[$i]); |
||||
$buffer = ''; |
||||
} |
||||
} |
||||
} |
||||
|
||||
$formulae = array(); |
||||
foreach ($calls as $call) { |
||||
$formulae += call_user_func_array(array($this, 'processCall'), $call); |
||||
} |
||||
|
||||
return $formulae; |
||||
} |
||||
|
||||
private function processCall($call, array $protoOptions = array()) |
||||
{ |
||||
$tmp = tempnam(sys_get_temp_dir(), 'assetic'); |
||||
file_put_contents($tmp, implode("\n", array( |
||||
'<?php', |
||||
$this->registerSetupCode(), |
||||
$call, |
||||
'echo serialize($_call);', |
||||
))); |
||||
$args = unserialize(shell_exec('php '.escapeshellarg($tmp))); |
||||
unlink($tmp); |
||||
|
||||
$inputs = isset($args[0]) ? self::argumentToArray($args[0]) : array(); |
||||
$filters = isset($args[1]) ? self::argumentToArray($args[1]) : array(); |
||||
$options = isset($args[2]) ? $args[2] : array(); |
||||
|
||||
if (!isset($options['debug'])) { |
||||
$options['debug'] = $this->factory->isDebug(); |
||||
} |
||||
|
||||
if (!is_array($options)) { |
||||
throw new \RuntimeException('The third argument must be omitted, null or an array.'); |
||||
} |
||||
|
||||
// apply the prototype options |
||||
$options += $protoOptions; |
||||
|
||||
if (!isset($options['name'])) { |
||||
$options['name'] = $this->factory->generateAssetName($inputs, $filters, $options); |
||||
} |
||||
|
||||
return array($options['name'] => array($inputs, $filters, $options)); |
||||
} |
||||
|
||||
/** |
||||
* Returns an array of prototypical calls and options. |
||||
* |
||||
* @return array Prototypes and options |
||||
*/ |
||||
abstract protected function registerPrototypes(); |
||||
|
||||
/** |
||||
* Returns setup code for the reflection scriptlet. |
||||
* |
||||
* @return string Some PHP setup code |
||||
*/ |
||||
abstract protected function registerSetupCode(); |
||||
|
||||
protected static function tokenToString($token) |
||||
{ |
||||
return is_array($token) ? $token[1] : $token; |
||||
} |
||||
|
||||
protected static function argumentToArray($argument) |
||||
{ |
||||
return is_array($argument) ? $argument : array_filter(array_map('trim', explode(',', $argument))); |
||||
} |
||||
} |
||||
@ -0,0 +1,68 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Factory\Loader; |
||||
|
||||
use Assetic\Cache\ConfigCache; |
||||
use Assetic\Factory\Resource\IteratorResourceInterface; |
||||
use Assetic\Factory\Resource\ResourceInterface; |
||||
|
||||
/** |
||||
* Adds a caching layer to a loader. |
||||
* |
||||
* A cached formula loader is a composition of a formula loader and a cache. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class CachedFormulaLoader implements FormulaLoaderInterface |
||||
{ |
||||
private $loader; |
||||
private $configCache; |
||||
private $debug; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* When the loader is in debug mode it will ensure the cached formulae |
||||
* are fresh before returning them. |
||||
* |
||||
* @param FormulaLoaderInterface $loader A formula loader |
||||
* @param ConfigCache $configCache A config cache |
||||
* @param Boolean $debug The debug mode |
||||
*/ |
||||
public function __construct(FormulaLoaderInterface $loader, ConfigCache $configCache, $debug = false) |
||||
{ |
||||
$this->loader = $loader; |
||||
$this->configCache = $configCache; |
||||
$this->debug = $debug; |
||||
} |
||||
|
||||
public function load(ResourceInterface $resources) |
||||
{ |
||||
if (!$resources instanceof IteratorResourceInterface) { |
||||
$resources = array($resources); |
||||
} |
||||
|
||||
$formulae = array(); |
||||
|
||||
foreach ($resources as $resource) { |
||||
$id = (string) $resource; |
||||
if (!$this->configCache->has($id) || ($this->debug && !$resource->isFresh($this->configCache->getTimestamp($id)))) { |
||||
$formulae += $this->loader->load($resource); |
||||
$this->configCache->set($id, $formulae); |
||||
} else { |
||||
$formulae += $this->configCache->get($id); |
||||
} |
||||
} |
||||
|
||||
return $formulae; |
||||
} |
||||
} |
||||
@ -0,0 +1,34 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Factory\Loader; |
||||
|
||||
use Assetic\Factory\Resource\ResourceInterface; |
||||
|
||||
/** |
||||
* Loads formulae. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
interface FormulaLoaderInterface |
||||
{ |
||||
/** |
||||
* Loads formulae from a resource. |
||||
* |
||||
* Formulae should be loaded the same regardless of the current debug |
||||
* mode. Debug considerations should happen downstream. |
||||
* |
||||
* @param ResourceInterface $resource A resource |
||||
* |
||||
* @return array An array of formulae |
||||
*/ |
||||
public function load(ResourceInterface $resource); |
||||
} |
||||
@ -0,0 +1,53 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Factory\Loader; |
||||
|
||||
/** |
||||
* Loads asset formulae from PHP files. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class FunctionCallsFormulaLoader extends BasePhpFormulaLoader |
||||
{ |
||||
protected function registerPrototypes() |
||||
{ |
||||
return array( |
||||
'assetic_javascripts(*)' => array('output' => 'js/*.js'), |
||||
'assetic_stylesheets(*)' => array('output' => 'css/*.css'), |
||||
'assetic_image(*)' => array('output' => 'images/*'), |
||||
); |
||||
} |
||||
|
||||
protected function registerSetupCode() |
||||
{ |
||||
return <<<'EOF' |
||||
function assetic_javascripts() |
||||
{ |
||||
global $_call; |
||||
$_call = func_get_args(); |
||||
} |
||||
|
||||
function assetic_stylesheets() |
||||
{ |
||||
global $_call; |
||||
$_call = func_get_args(); |
||||
} |
||||
|
||||
function assetic_image() |
||||
{ |
||||
global $_call; |
||||
$_call = func_get_args(); |
||||
} |
||||
|
||||
EOF; |
||||
} |
||||
} |
||||
@ -0,0 +1,112 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Factory\Resource; |
||||
|
||||
/** |
||||
* Coalesces multiple directories together into one merged resource. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class CoalescingDirectoryResource implements IteratorResourceInterface |
||||
{ |
||||
private $directories; |
||||
|
||||
public function __construct($directories) |
||||
{ |
||||
$this->directories = array(); |
||||
|
||||
foreach ($directories as $directory) { |
||||
$this->addDirectory($directory); |
||||
} |
||||
} |
||||
|
||||
public function addDirectory(IteratorResourceInterface $directory) |
||||
{ |
||||
$this->directories[] = $directory; |
||||
} |
||||
|
||||
public function isFresh($timestamp) |
||||
{ |
||||
foreach ($this->getFileResources() as $file) { |
||||
if (!$file->isFresh($timestamp)) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
public function getContent() |
||||
{ |
||||
$parts = array(); |
||||
foreach ($this->getFileResources() as $file) { |
||||
$parts[] = $file->getContent(); |
||||
} |
||||
|
||||
return implode("\n", $parts); |
||||
} |
||||
|
||||
/** |
||||
* Returns a string to uniquely identify the current resource. |
||||
* |
||||
* @return string An identifying string |
||||
*/ |
||||
public function __toString() |
||||
{ |
||||
$parts = array(); |
||||
foreach ($this->directories as $directory) { |
||||
$parts[] = (string) $directory; |
||||
} |
||||
|
||||
return implode(',', $parts); |
||||
} |
||||
|
||||
public function getIterator() |
||||
{ |
||||
return new \ArrayIterator($this->getFileResources()); |
||||
} |
||||
|
||||
/** |
||||
* Returns the relative version of a filename. |
||||
* |
||||
* @param ResourceInterface $file The file |
||||
* @param ResourceInterface $directory The directory |
||||
* |
||||
* @return string The name to compare with files from other directories |
||||
*/ |
||||
protected function getRelativeName(ResourceInterface $file, ResourceInterface $directory) |
||||
{ |
||||
return substr((string) $file, strlen((string) $directory)); |
||||
} |
||||
|
||||
/** |
||||
* Performs the coalesce. |
||||
* |
||||
* @return array An array of file resources |
||||
*/ |
||||
private function getFileResources() |
||||
{ |
||||
$paths = array(); |
||||
|
||||
foreach ($this->directories as $directory) { |
||||
foreach ($directory as $file) { |
||||
$relative = $this->getRelativeName($file, $directory); |
||||
|
||||
if (!isset($paths[$relative])) { |
||||
$paths[$relative] = $file; |
||||
} |
||||
} |
||||
} |
||||
|
||||
return array_values($paths); |
||||
} |
||||
} |
||||
@ -0,0 +1,133 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Factory\Resource; |
||||
|
||||
/** |
||||
* A resource is something formulae can be loaded from. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class DirectoryResource implements IteratorResourceInterface |
||||
{ |
||||
private $path; |
||||
private $pattern; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param string $path A directory path |
||||
* @param string $pattern A filename pattern |
||||
*/ |
||||
public function __construct($path, $pattern = null) |
||||
{ |
||||
if (DIRECTORY_SEPARATOR != substr($path, -1)) { |
||||
$path .= DIRECTORY_SEPARATOR; |
||||
} |
||||
|
||||
$this->path = $path; |
||||
$this->pattern = $pattern; |
||||
} |
||||
|
||||
public function isFresh($timestamp) |
||||
{ |
||||
if (!is_dir($this->path) || filemtime($this->path) > $timestamp) { |
||||
return false; |
||||
} |
||||
|
||||
foreach ($this as $resource) { |
||||
if (!$resource->isFresh($timestamp)) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* Returns the combined content of all inner resources. |
||||
*/ |
||||
public function getContent() |
||||
{ |
||||
$content = array(); |
||||
foreach ($this as $resource) { |
||||
$content[] = $resource->getContent(); |
||||
} |
||||
|
||||
return implode("\n", $content); |
||||
} |
||||
|
||||
public function __toString() |
||||
{ |
||||
return $this->path; |
||||
} |
||||
|
||||
public function getIterator() |
||||
{ |
||||
return is_dir($this->path) |
||||
? new DirectoryResourceIterator($this->getInnerIterator()) |
||||
: new \EmptyIterator(); |
||||
} |
||||
|
||||
protected function getInnerIterator() |
||||
{ |
||||
return new DirectoryResourceFilterIterator(new \RecursiveDirectoryIterator($this->path, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS), $this->pattern); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* An iterator that converts file objects into file resources. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
* @access private |
||||
*/ |
||||
class DirectoryResourceIterator extends \RecursiveIteratorIterator |
||||
{ |
||||
public function current() |
||||
{ |
||||
return new FileResource(parent::current()->getPathname()); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Filters files by a basename pattern. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
* @access private |
||||
*/ |
||||
class DirectoryResourceFilterIterator extends \RecursiveFilterIterator |
||||
{ |
||||
protected $pattern; |
||||
|
||||
public function __construct(\RecursiveDirectoryIterator $iterator, $pattern = null) |
||||
{ |
||||
parent::__construct($iterator); |
||||
|
||||
$this->pattern = $pattern; |
||||
} |
||||
|
||||
public function accept() |
||||
{ |
||||
$file = $this->current(); |
||||
$name = $file->getBasename(); |
||||
|
||||
if ($file->isDir()) { |
||||
return '.' != $name[0]; |
||||
} else { |
||||
return null === $this->pattern || 0 < preg_match($this->pattern, $name); |
||||
} |
||||
} |
||||
|
||||
public function getChildren() |
||||
{ |
||||
return new self(new \RecursiveDirectoryIterator($this->current()->getPathname(), \RecursiveDirectoryIterator::FOLLOW_SYMLINKS), $this->pattern); |
||||
} |
||||
} |
||||
@ -0,0 +1,47 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Factory\Resource; |
||||
|
||||
/** |
||||
* A resource is something formulae can be loaded from. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class FileResource implements ResourceInterface |
||||
{ |
||||
private $path; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param string $path The path to a file |
||||
*/ |
||||
public function __construct($path) |
||||
{ |
||||
$this->path = $path; |
||||
} |
||||
|
||||
public function isFresh($timestamp) |
||||
{ |
||||
return file_exists($this->path) && filemtime($this->path) <= $timestamp; |
||||
} |
||||
|
||||
public function getContent() |
||||
{ |
||||
return file_exists($this->path) ? file_get_contents($this->path) : ''; |
||||
} |
||||
|
||||
public function __toString() |
||||
{ |
||||
return $this->path; |
||||
} |
||||
} |
||||
@ -0,0 +1,21 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Factory\Resource; |
||||
|
||||
/** |
||||
* A resource is something formulae can be loaded from. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
interface IteratorResourceInterface extends ResourceInterface, \IteratorAggregate |
||||
{ |
||||
} |
||||
@ -0,0 +1,43 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Factory\Resource; |
||||
|
||||
/** |
||||
* A resource is something formulae can be loaded from. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
interface ResourceInterface |
||||
{ |
||||
/** |
||||
* Checks if a timestamp represents the latest resource. |
||||
* |
||||
* @param integer $timestamp A UNIX timestamp |
||||
* |
||||
* @return Boolean True if the timestamp is up to date |
||||
*/ |
||||
public function isFresh($timestamp); |
||||
|
||||
/** |
||||
* Returns the content of the resource. |
||||
* |
||||
* @return string The content |
||||
*/ |
||||
public function getContent(); |
||||
|
||||
/** |
||||
* Returns a unique string for the current resource. |
||||
* |
||||
* @return string A unique string to identity the current resource |
||||
*/ |
||||
public function __toString(); |
||||
} |
||||
@ -0,0 +1,60 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Factory\Worker; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
use Assetic\Filter\FilterInterface; |
||||
|
||||
/** |
||||
* Applies a filter to an asset based on a source and/or target path match. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
* @todo A better asset-matcher mechanism |
||||
*/ |
||||
class EnsureFilterWorker implements WorkerInterface |
||||
{ |
||||
const CHECK_SOURCE = 1; |
||||
const CHECK_TARGET = 2; |
||||
|
||||
private $pattern; |
||||
private $filter; |
||||
private $flags; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param string $pattern A regex for checking the asset's target URL |
||||
* @param FilterInterface $filter A filter to apply if the regex matches |
||||
* @param integer $flags Flags for what to check |
||||
*/ |
||||
public function __construct($pattern, FilterInterface $filter, $flags = null) |
||||
{ |
||||
if (null === $flags) { |
||||
$flags = self::CHECK_SOURCE | self::CHECK_TARGET; |
||||
} |
||||
|
||||
$this->pattern = $pattern; |
||||
$this->filter = $filter; |
||||
$this->flags = $flags; |
||||
} |
||||
|
||||
public function process(AssetInterface $asset) |
||||
{ |
||||
if ( |
||||
(self::CHECK_SOURCE === (self::CHECK_SOURCE & $this->flags) && preg_match($this->pattern, $asset->getSourcePath())) |
||||
|| |
||||
(self::CHECK_TARGET === (self::CHECK_TARGET & $this->flags) && preg_match($this->pattern, $asset->getTargetPath())) |
||||
) { |
||||
$asset->ensureFilter($this->filter); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,29 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Factory\Worker; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
|
||||
/** |
||||
* Assets are passed through factory workers before leaving the factory. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
interface WorkerInterface |
||||
{ |
||||
/** |
||||
* Processes an asset. |
||||
* |
||||
* @param AssetInterface $asset An asset |
||||
*/ |
||||
public function process(AssetInterface $asset); |
||||
} |
||||
@ -0,0 +1,71 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Filter; |
||||
|
||||
/** |
||||
* An abstract filter for dealing with CSS. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
abstract class BaseCssFilter implements FilterInterface |
||||
{ |
||||
/** |
||||
* Filters all references -- url() and "@import" -- through a callable. |
||||
* |
||||
* @param string $content The CSS |
||||
* @param mixed $callback A PHP callable |
||||
* |
||||
* @return string The filtered CSS |
||||
*/ |
||||
protected function filterReferences($content, $callback, $limit = -1, & $count = 0) |
||||
{ |
||||
$content = $this->filterUrls($content, $callback, $limit, $count); |
||||
$content = $this->filterImports($content, $callback, $limit, $count, false); |
||||
|
||||
return $content; |
||||
} |
||||
|
||||
/** |
||||
* Filters all CSS url()'s through a callable. |
||||
* |
||||
* @param string $content The CSS |
||||
* @param mixed $callback A PHP callable |
||||
* @param integer $limit Limit the number of replacements |
||||
* @param integer $count Will be populated with the count |
||||
* |
||||
* @return string The filtered CSS |
||||
*/ |
||||
protected function filterUrls($content, $callback, $limit = -1, & $count = 0) |
||||
{ |
||||
return preg_replace_callback('/url\((["\']?)(?<url>.*?)(\\1)\)/', $callback, $content, $limit, $count); |
||||
} |
||||
|
||||
/** |
||||
* Filters all CSS imports through a callable. |
||||
* |
||||
* @param string $content The CSS |
||||
* @param mixed $callback A PHP callable |
||||
* @param integer $limit Limit the number of replacements |
||||
* @param integer $count Will be populated with the count |
||||
* @param Boolean $includeUrl Whether to include url() in the pattern |
||||
* |
||||
* @return string The filtered CSS |
||||
*/ |
||||
protected function filterImports($content, $callback, $limit = -1, & $count = 0, $includeUrl = true) |
||||
{ |
||||
$pattern = $includeUrl |
||||
? '/@import (?:url\()?(\'|"|)(?<url>[^\'"\)\n\r]*)\1\)?;?/' |
||||
: '/@import (?!url\()(\'|"|)(?<url>[^\'"\)\n\r]*)\1;?/'; |
||||
|
||||
return preg_replace_callback($pattern, $callback, $content, $limit, $count); |
||||
} |
||||
} |
||||
@ -0,0 +1,45 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Filter; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
|
||||
/** |
||||
* A filter that wraps callables. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class CallablesFilter implements FilterInterface |
||||
{ |
||||
private $loader; |
||||
private $dumper; |
||||
|
||||
public function __construct($loader = null, $dumper = null) |
||||
{ |
||||
$this->loader = $loader; |
||||
$this->dumper = $dumper; |
||||
} |
||||
|
||||
public function filterLoad(AssetInterface $asset) |
||||
{ |
||||
if (null !== $callable = $this->loader) { |
||||
$callable($asset); |
||||
} |
||||
} |
||||
|
||||
public function filterDump(AssetInterface $asset) |
||||
{ |
||||
if (null !== $callable = $this->dumper) { |
||||
$callable($asset); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,60 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Filter; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
use Assetic\Util\ProcessBuilder; |
||||
|
||||
/** |
||||
* Compiles CoffeeScript into Javascript. |
||||
* |
||||
* @link http://jashkenas.github.com/coffee-script/ |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class CoffeeScriptFilter implements FilterInterface |
||||
{ |
||||
private $coffeePath; |
||||
private $nodePath; |
||||
|
||||
public function __construct($coffeePath = '/usr/bin/coffee', $nodePath = '/usr/bin/node') |
||||
{ |
||||
$this->coffeePath = $coffeePath; |
||||
$this->nodePath = $nodePath; |
||||
} |
||||
|
||||
public function filterLoad(AssetInterface $asset) |
||||
{ |
||||
$input = tempnam(sys_get_temp_dir(), 'assetic_coffeescript'); |
||||
file_put_contents($input, $asset->getContent()); |
||||
|
||||
$pb = new ProcessBuilder(array( |
||||
$this->nodePath, |
||||
$this->coffeePath, |
||||
'-cp', |
||||
$input, |
||||
)); |
||||
|
||||
$proc = $pb->getProcess(); |
||||
$code = $proc->run(); |
||||
unlink($input); |
||||
|
||||
if (0 < $code) { |
||||
throw new \RuntimeException($proc->getErrorOutput()); |
||||
} |
||||
|
||||
$asset->setContent($proc->getOutput()); |
||||
} |
||||
|
||||
public function filterDump(AssetInterface $asset) |
||||
{ |
||||
} |
||||
} |
||||
@ -0,0 +1,334 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Filter; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
use Assetic\Filter\FilterInterface; |
||||
use Assetic\Util\ProcessBuilder; |
||||
|
||||
/** |
||||
* Loads Compass files. |
||||
* |
||||
* @author Maxime Thirouin <maxime.thirouin@gmail.com> |
||||
*/ |
||||
class CompassFilter implements FilterInterface |
||||
{ |
||||
private $compassPath; |
||||
private $scss; |
||||
|
||||
// sass options |
||||
private $unixNewlines; |
||||
private $debugInfo; |
||||
private $cacheLocation; |
||||
private $noCache; |
||||
|
||||
// compass options |
||||
private $force; |
||||
private $style; |
||||
private $quiet; |
||||
private $boring; |
||||
private $noLineComments; |
||||
private $imagesDir; |
||||
private $javascriptsDir; |
||||
|
||||
// compass configuration file options |
||||
private $plugins = array(); |
||||
private $loadPaths = array(); |
||||
private $httpPath; |
||||
private $httpImagesPath; |
||||
private $httpJavascriptsPath; |
||||
|
||||
public function __construct($compassPath = '/usr/bin/compass') |
||||
{ |
||||
$this->compassPath = $compassPath; |
||||
$this->cacheLocation = sys_get_temp_dir(); |
||||
|
||||
if ('cli' !== php_sapi_name()) { |
||||
$this->boring = true; |
||||
} |
||||
} |
||||
|
||||
public function setScss($scss) |
||||
{ |
||||
$this->scss = $scss; |
||||
} |
||||
|
||||
// sass options setters |
||||
public function setUnixNewlines($unixNewlines) |
||||
{ |
||||
$this->unixNewlines = $unixNewlines; |
||||
} |
||||
|
||||
public function setDebugInfo($debugInfo) |
||||
{ |
||||
$this->debugInfo = $debugInfo; |
||||
} |
||||
|
||||
public function setCacheLocation($cacheLocation) |
||||
{ |
||||
$this->cacheLocation = $cacheLocation; |
||||
} |
||||
|
||||
public function setNoCache($noCache) |
||||
{ |
||||
$this->noCache = $noCache; |
||||
} |
||||
|
||||
// compass options setters |
||||
public function setForce($force) |
||||
{ |
||||
$this->force = $force; |
||||
} |
||||
|
||||
public function setStyle($style) |
||||
{ |
||||
$this->style = $style; |
||||
} |
||||
|
||||
public function setQuiet($quiet) |
||||
{ |
||||
$this->quiet = $quiet; |
||||
} |
||||
|
||||
public function setBoring($boring) |
||||
{ |
||||
$this->boring = $boring; |
||||
} |
||||
|
||||
public function setNoLineComments($noLineComments) |
||||
{ |
||||
$this->noLineComments = $noLineComments; |
||||
} |
||||
|
||||
public function setImagesDir($imagesDir) |
||||
{ |
||||
$this->imagesDir = $imagesDir; |
||||
} |
||||
|
||||
public function setJavascriptsDir($javascriptsDir) |
||||
{ |
||||
$this->javascriptsDir = $javascriptsDir; |
||||
} |
||||
|
||||
// compass configuration file options setters |
||||
public function setPlugins(array $plugins) |
||||
{ |
||||
$this->plugins = $plugins; |
||||
} |
||||
|
||||
public function addPlugin($plugin) |
||||
{ |
||||
$this->plugins[] = $plugin; |
||||
} |
||||
|
||||
public function addLoadPath($loadPath) |
||||
{ |
||||
$this->loadPaths[] = $loadPath; |
||||
} |
||||
|
||||
public function setHttpPath($httpPath) |
||||
{ |
||||
$this->httpPath = $httpPath; |
||||
} |
||||
|
||||
public function setHttpImagesPath($httpImagesPath) |
||||
{ |
||||
$this->httpImagesPath = $httpImagesPath; |
||||
} |
||||
|
||||
public function setHttpJavascriptsPath($httpJavascriptsPath) |
||||
{ |
||||
$this->httpJavascriptsPath = $httpJavascriptsPath; |
||||
} |
||||
|
||||
public function filterLoad(AssetInterface $asset) |
||||
{ |
||||
$root = $asset->getSourceRoot(); |
||||
$path = $asset->getSourcePath(); |
||||
|
||||
$loadPaths = $this->loadPaths; |
||||
if ($root && $path) { |
||||
$loadPaths[] = dirname($root.'/'.$path); |
||||
} |
||||
|
||||
// compass does not seems to handle symlink, so we use realpath() |
||||
$tempDir = realpath(sys_get_temp_dir()); |
||||
|
||||
$pb = new ProcessBuilder(array( |
||||
$this->compassPath, |
||||
'compile', |
||||
$tempDir, |
||||
)); |
||||
$pb->inheritEnvironmentVariables(); |
||||
|
||||
if ($this->force) { |
||||
$pb->add('--force'); |
||||
} |
||||
|
||||
if ($this->style) { |
||||
$pb->add('--output-style')->add($this->style); |
||||
} |
||||
|
||||
if ($this->quiet) { |
||||
$pb->add('--quiet'); |
||||
} |
||||
|
||||
if ($this->boring) { |
||||
$pb->add('--boring'); |
||||
} |
||||
|
||||
if ($this->noLineComments) { |
||||
$pb->add('--no-line-comments'); |
||||
} |
||||
|
||||
// these two options are not passed into the config file |
||||
// because like this, compass adapts this to be xxx_dir or xxx_path |
||||
// whether it's an absolute path or not |
||||
if ($this->imagesDir) { |
||||
$pb->add('--images-dir')->add($this->imagesDir); |
||||
} |
||||
|
||||
if ($this->javascriptsDir) { |
||||
$pb->add('--javascripts-dir')->add($this->javascriptsDir); |
||||
} |
||||
|
||||
// options in config file |
||||
$optionsConfig = array(); |
||||
|
||||
if (!empty($loadPaths)) { |
||||
$optionsConfig['additional_import_paths'] = $loadPaths; |
||||
} |
||||
|
||||
if ($this->unixNewlines) { |
||||
$optionsConfig['sass_options']['unix_newlines'] = true; |
||||
} |
||||
|
||||
if ($this->debugInfo) { |
||||
$optionsConfig['sass_options']['debug_info'] = true; |
||||
} |
||||
|
||||
if ($this->cacheLocation) { |
||||
$optionsConfig['sass_options']['cache_location'] = $this->cacheLocation; |
||||
} |
||||
|
||||
if ($this->noCache) { |
||||
$optionsConfig['sass_options']['no_cache'] = true; |
||||
} |
||||
|
||||
if ($this->httpPath) { |
||||
$optionsConfig['http_path'] = $this->httpPath; |
||||
} |
||||
|
||||
if ($this->httpImagesPath) { |
||||
$optionsConfig['http_images_path'] = $this->httpImagesPath; |
||||
} |
||||
|
||||
if ($this->httpJavascriptsPath) { |
||||
$optionsConfig['http_javascripts_path'] = $this->httpJavascriptsPath; |
||||
} |
||||
|
||||
// options in configuration file |
||||
if (count($optionsConfig)) { |
||||
$config = array(); |
||||
foreach ($this->plugins as $plugin) { |
||||
$config[] = sprintf("require '%s'", addcslashes($plugin, '\\')); |
||||
} |
||||
foreach ($optionsConfig as $name => $value) { |
||||
if (!is_array($value)) { |
||||
$config[] = sprintf('%s = "%s"', $name, addcslashes($value, '\\')); |
||||
} elseif (!empty($value)) { |
||||
$config[] = sprintf('%s = %s', $name, $this->formatArrayToRuby($value)); |
||||
} |
||||
} |
||||
|
||||
$configFile = tempnam($tempDir, 'assetic_compass'); |
||||
file_put_contents($configFile, implode("\n", $config)."\n"); |
||||
$pb->add('--config')->add($configFile); |
||||
} |
||||
|
||||
$pb->add('--sass-dir')->add('')->add('--css-dir')->add(''); |
||||
|
||||
// compass choose the type (sass or scss from the filename) |
||||
if (null !== $this->scss) { |
||||
$type = $this->scss ? 'scss' : 'sass'; |
||||
} elseif ($path) { |
||||
// FIXME: what if the extension is something else? |
||||
$type = pathinfo($path, PATHINFO_EXTENSION); |
||||
} else { |
||||
$type = 'scss'; |
||||
} |
||||
|
||||
$tempName = tempnam($tempDir, 'assetic_compass'); |
||||
unlink($tempName); // FIXME: don't use tempnam() here |
||||
|
||||
// input |
||||
$input = $tempName.'.'.$type; |
||||
|
||||
// work-around for https://github.com/chriseppstein/compass/issues/748 |
||||
if (defined('PHP_WINDOWS_VERSION_MAJOR')) { |
||||
$input = str_replace('\\', '/', $input); |
||||
} |
||||
|
||||
$pb->add($input); |
||||
file_put_contents($input, $asset->getContent()); |
||||
|
||||
// output |
||||
$output = $tempName.'.css'; |
||||
|
||||
// it's not really usefull but... https://github.com/chriseppstein/compass/issues/376 |
||||
$pb->setEnv('HOME', sys_get_temp_dir()); |
||||
|
||||
$proc = $pb->getProcess(); |
||||
$code = $proc->run(); |
||||
|
||||
if (0 < $code) { |
||||
unlink($input); |
||||
if (isset($configFile)) { |
||||
unlink($configFile); |
||||
} |
||||
|
||||
throw new \RuntimeException($proc->getErrorOutput().'...'.$proc->getOutput()); |
||||
} |
||||
|
||||
$asset->setContent(file_get_contents($output)); |
||||
|
||||
unlink($input); |
||||
unlink($output); |
||||
if (isset($configFile)) { |
||||
unlink($configFile); |
||||
} |
||||
} |
||||
|
||||
public function filterDump(AssetInterface $asset) |
||||
{ |
||||
} |
||||
|
||||
private function formatArrayToRuby($array) |
||||
{ |
||||
$output = array(); |
||||
|
||||
// does we have an associative array ? |
||||
if (count(array_filter(array_keys($array), "is_numeric")) != count($array)) { |
||||
foreach ($array as $name => $value) { |
||||
$output[] = sprintf(' :%s => "%s"', $name, addcslashes($value, '\\')); |
||||
} |
||||
$output = "{\n".implode(",\n", $output)."\n}"; |
||||
} else { |
||||
foreach ($array as $name => $value) { |
||||
$output[] = sprintf(' "%s"', addcslashes($value, '\\')); |
||||
} |
||||
$output = "[\n".implode(",\n", $output)."\n]"; |
||||
} |
||||
|
||||
return $output; |
||||
} |
||||
} |
||||
@ -0,0 +1,138 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Filter; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
use Assetic\Filter\FilterInterface; |
||||
use Assetic\Util\ProcessBuilder; |
||||
|
||||
/** |
||||
* CSSEmbed filter |
||||
* |
||||
* @author Maxime Thirouin <maxime.thirouin@gmail.com> |
||||
*/ |
||||
class CssEmbedFilter implements FilterInterface |
||||
{ |
||||
private $jarPath; |
||||
private $javaPath; |
||||
private $charset; |
||||
private $mhtml; // Enable MHTML mode. |
||||
private $mhtmlRoot; // Use <root> as the MHTML root for the file. |
||||
private $root; // Prepends <root> to all relative URLs. |
||||
private $skipMissing; // Don't throw an error for missing image files. |
||||
private $maxUriLength; // Maximum length for a data URI. Defaults to 32768. |
||||
private $maxImageSize; // Maximum image size (in bytes) to convert. |
||||
|
||||
public function __construct($jarPath, $javaPath = '/usr/bin/java') |
||||
{ |
||||
$this->jarPath = $jarPath; |
||||
$this->javaPath = $javaPath; |
||||
} |
||||
|
||||
public function setCharset($charset) |
||||
{ |
||||
$this->charset = $charset; |
||||
} |
||||
|
||||
public function setMhtml($mhtml) |
||||
{ |
||||
$this->mhtml = $mhtml; |
||||
} |
||||
|
||||
public function setMhtmlRoot($mhtmlRoot) |
||||
{ |
||||
$this->mhtmlRoot = $mhtmlRoot; |
||||
} |
||||
|
||||
public function setRoot($root) |
||||
{ |
||||
$this->root = $root; |
||||
} |
||||
|
||||
public function setSkipMissing($skipMissing) |
||||
{ |
||||
$this->skipMissing = $skipMissing; |
||||
} |
||||
|
||||
public function setMaxUriLength($maxUriLength) |
||||
{ |
||||
$this->maxUriLength = $maxUriLength; |
||||
} |
||||
|
||||
public function setMaxImageSize($maxImageSize) |
||||
{ |
||||
$this->maxImageSize = $maxImageSize; |
||||
} |
||||
|
||||
public function filterLoad(AssetInterface $asset) |
||||
{ |
||||
} |
||||
|
||||
public function filterDump(AssetInterface $asset) |
||||
{ |
||||
$pb = new ProcessBuilder(array( |
||||
$this->javaPath, |
||||
'-jar', |
||||
$this->jarPath, |
||||
)); |
||||
|
||||
if (null !== $this->charset) { |
||||
$pb->add('--charset')->add($this->charset); |
||||
} |
||||
|
||||
if ($this->mhtml) { |
||||
$pb->add('--mhtml'); |
||||
} |
||||
|
||||
if (null !== $this->mhtmlRoot) { |
||||
$pb->add('--mhtmlroot')->add($this->mhtmlRoot); |
||||
} |
||||
|
||||
// automatically define root if not already defined |
||||
if (null === $this->root) { |
||||
$root = $asset->getSourceRoot(); |
||||
$path = $asset->getSourcePath(); |
||||
|
||||
if ($root && $path) { |
||||
$pb->add('--root')->add(dirname($root.'/'.$path)); |
||||
} |
||||
} else { |
||||
$pb->add('--root')->add($this->root); |
||||
} |
||||
|
||||
if ($this->skipMissing) { |
||||
$pb->add('--skip-missing'); |
||||
} |
||||
|
||||
if (null !== $this->maxUriLength) { |
||||
$pb->add('--max-uri-length')->add($this->maxUriLength); |
||||
} |
||||
|
||||
if (null !== $this->maxImageSize) { |
||||
$pb->add('--max-image-size')->add($this->maxImageSize); |
||||
} |
||||
|
||||
// input |
||||
$pb->add($input = tempnam(sys_get_temp_dir(), 'assetic_cssembed')); |
||||
file_put_contents($input, $asset->getContent()); |
||||
|
||||
$proc = $pb->getProcess(); |
||||
$code = $proc->run(); |
||||
unlink($input); |
||||
|
||||
if (0 < $code) { |
||||
throw new \RuntimeException($proc->getErrorOutput()); |
||||
} |
||||
|
||||
$asset->setContent($proc->getOutput()); |
||||
} |
||||
} |
||||
@ -0,0 +1,106 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Filter; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
use Assetic\Asset\FileAsset; |
||||
use Assetic\Asset\HttpAsset; |
||||
|
||||
/** |
||||
* Inlines imported stylesheets. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class CssImportFilter extends BaseCssFilter |
||||
{ |
||||
private $importFilter; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param FilterInterface $importFilter Filter for each imported asset |
||||
*/ |
||||
public function __construct(FilterInterface $importFilter = null) |
||||
{ |
||||
$this->importFilter = $importFilter ?: new CssRewriteFilter(); |
||||
} |
||||
|
||||
public function filterLoad(AssetInterface $asset) |
||||
{ |
||||
$importFilter = $this->importFilter; |
||||
$sourceRoot = $asset->getSourceRoot(); |
||||
$sourcePath = $asset->getSourcePath(); |
||||
|
||||
$callback = function($matches) use ($importFilter, $sourceRoot, $sourcePath) { |
||||
if (!$matches['url'] || null === $sourceRoot) { |
||||
return $matches[0]; |
||||
} |
||||
|
||||
$importRoot = $sourceRoot; |
||||
|
||||
if (false !== strpos($matches['url'], '://')) { |
||||
// absolute |
||||
list($importScheme, $tmp) = explode('://', $matches['url'], 2); |
||||
list($importHost, $importPath) = explode('/', $tmp, 2); |
||||
$importRoot = $importScheme.'://'.$importHost; |
||||
} elseif (0 === strpos($matches['url'], '//')) { |
||||
// protocol-relative |
||||
list($importHost, $importPath) = explode('/', substr($matches['url'], 2), 2); |
||||
$importHost = '//'.$importHost; |
||||
} elseif ('/' == $matches['url'][0]) { |
||||
// root-relative |
||||
$importPath = substr($matches['url'], 1); |
||||
} elseif (null !== $sourcePath) { |
||||
// document-relative |
||||
$importPath = $matches['url']; |
||||
if ('.' != $sourceDir = dirname($sourcePath)) { |
||||
$importPath = $sourceDir.'/'.$importPath; |
||||
} |
||||
} else { |
||||
return $matches[0]; |
||||
} |
||||
|
||||
// ignore other imports |
||||
if ('css' != pathinfo($importPath, PATHINFO_EXTENSION)) { |
||||
return $matches[0]; |
||||
} |
||||
|
||||
$importSource = $importRoot.'/'.$importPath; |
||||
if (false !== strpos($importSource, '://') || 0 === strpos($importSource, '//')) { |
||||
$import = new HttpAsset($importSource, array($importFilter), true); |
||||
} elseif (!file_exists($importSource)) { |
||||
// ignore not found imports |
||||
return $matches[0]; |
||||
} else { |
||||
$import = new FileAsset($importSource, array($importFilter), $importRoot, $importPath); |
||||
} |
||||
|
||||
$import->setTargetPath($sourcePath); |
||||
|
||||
return $import->dump(); |
||||
}; |
||||
|
||||
$content = $asset->getContent(); |
||||
$lastHash = md5($content); |
||||
|
||||
do { |
||||
$content = $this->filterImports($content, $callback); |
||||
$hash = md5($content); |
||||
} while ($lastHash != $hash && $lastHash = $hash); |
||||
|
||||
$asset->setContent($content); |
||||
} |
||||
|
||||
public function filterDump(AssetInterface $asset) |
||||
{ |
||||
} |
||||
} |
||||
@ -0,0 +1,73 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Filter; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
|
||||
/** |
||||
* Filters assets through CssMin. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class CssMinFilter implements FilterInterface |
||||
{ |
||||
private $filters; |
||||
private $plugins; |
||||
|
||||
public function __construct() |
||||
{ |
||||
$this->filters = array(); |
||||
$this->plugins = array(); |
||||
} |
||||
|
||||
public function setFilters(array $filters) |
||||
{ |
||||
$this->filters = $filters; |
||||
} |
||||
|
||||
public function setFilter($name, $value) |
||||
{ |
||||
$this->filters[$name] = $value; |
||||
} |
||||
|
||||
public function setPlugins(array $plugins) |
||||
{ |
||||
$this->plugins = $plugins; |
||||
} |
||||
|
||||
public function setPlugin($name, $value) |
||||
{ |
||||
$this->plugins[$name] = $value; |
||||
} |
||||
|
||||
public function filterLoad(AssetInterface $asset) |
||||
{ |
||||
} |
||||
|
||||
public function filterDump(AssetInterface $asset) |
||||
{ |
||||
$filters = $this->filters; |
||||
$plugins = $this->plugins; |
||||
|
||||
if (isset($filters['ImportImports']) && true === $filters['ImportImports']) { |
||||
$root = $asset->getSourceRoot(); |
||||
$path = $asset->getSourcePath(); |
||||
if ($root && $path) { |
||||
$filters['ImportImports'] = array('BasePath' => dirname($root.'/'.$path)); |
||||
} else { |
||||
unset($filters['ImportImports']); |
||||
} |
||||
} |
||||
|
||||
$asset->setContent(\CssMin::minify($asset->getContent(), $filters, $plugins)); |
||||
} |
||||
} |
||||
@ -0,0 +1,93 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Filter; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
|
||||
/** |
||||
* Fixes relative CSS urls. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class CssRewriteFilter extends BaseCssFilter |
||||
{ |
||||
public function filterLoad(AssetInterface $asset) |
||||
{ |
||||
} |
||||
|
||||
public function filterDump(AssetInterface $asset) |
||||
{ |
||||
$sourceBase = $asset->getSourceRoot(); |
||||
$sourcePath = $asset->getSourcePath(); |
||||
$targetPath = $asset->getTargetPath(); |
||||
|
||||
if (null === $sourcePath || null === $targetPath || $sourcePath == $targetPath) { |
||||
return; |
||||
} |
||||
|
||||
// learn how to get from the target back to the source |
||||
if (false !== strpos($sourceBase, '://')) { |
||||
list($scheme, $url) = explode('://', $sourceBase.'/'.$sourcePath, 2); |
||||
list($host, $path) = explode('/', $url, 2); |
||||
|
||||
$host = $scheme.'://'.$host.'/'; |
||||
$path = false === strpos($path, '/') ? '' : dirname($path); |
||||
$path .= '/'; |
||||
} else { |
||||
// assume source and target are on the same host |
||||
$host = ''; |
||||
|
||||
// pop entries off the target until it fits in the source |
||||
if ('.' == dirname($sourcePath)) { |
||||
$path = str_repeat('../', substr_count($targetPath, '/')); |
||||
} elseif ('.' == $targetDir = dirname($targetPath)) { |
||||
$path = dirname($sourcePath).'/'; |
||||
} else { |
||||
$path = ''; |
||||
while (0 !== strpos($sourcePath, $targetDir)) { |
||||
if (false !== $pos = strrpos($targetDir, '/')) { |
||||
$targetDir = substr($targetDir, 0, $pos); |
||||
$path .= '../'; |
||||
} else { |
||||
$targetDir = ''; |
||||
$path .= '../'; |
||||
break; |
||||
} |
||||
} |
||||
$path .= ltrim(substr(dirname($sourcePath).'/', strlen($targetDir)), '/'); |
||||
} |
||||
} |
||||
|
||||
$content = $this->filterReferences($asset->getContent(), function($matches) use ($host, $path) { |
||||
if (false !== strpos($matches['url'], '://') || 0 === strpos($matches['url'], '//') || 0 === strpos($matches['url'], 'data:')) { |
||||
// absolute or protocol-relative or data uri |
||||
return $matches[0]; |
||||
} |
||||
|
||||
if ('/' == $matches['url'][0]) { |
||||
// root relative |
||||
return str_replace($matches['url'], $host.$matches['url'], $matches[0]); |
||||
} |
||||
|
||||
// document relative |
||||
$url = $matches['url']; |
||||
while (0 === strpos($url, '../') && 2 <= substr_count($path, '/')) { |
||||
$path = substr($path, 0, strrpos(rtrim($path, '/'), '/') + 1); |
||||
$url = substr($url, 3); |
||||
} |
||||
|
||||
return str_replace($matches['url'], $host.$path.$url, $matches[0]); |
||||
}); |
||||
|
||||
$asset->setContent($content); |
||||
} |
||||
} |
||||
@ -0,0 +1,82 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Filter; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
|
||||
/** |
||||
* A collection of filters. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class FilterCollection implements FilterInterface, \IteratorAggregate, \Countable |
||||
{ |
||||
private $filters = array(); |
||||
|
||||
public function __construct($filters = array()) |
||||
{ |
||||
foreach ($filters as $filter) { |
||||
$this->ensure($filter); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Checks that the current collection contains the supplied filter. |
||||
* |
||||
* If the supplied filter is another filter collection, each of its |
||||
* filters will be checked. |
||||
*/ |
||||
public function ensure(FilterInterface $filter) |
||||
{ |
||||
if ($filter instanceof \Traversable) { |
||||
foreach ($filter as $f) { |
||||
$this->ensure($f); |
||||
} |
||||
} elseif (!in_array($filter, $this->filters, true)) { |
||||
$this->filters[] = $filter; |
||||
} |
||||
} |
||||
|
||||
public function all() |
||||
{ |
||||
return $this->filters; |
||||
} |
||||
|
||||
public function clear() |
||||
{ |
||||
$this->filters = array(); |
||||
} |
||||
|
||||
public function filterLoad(AssetInterface $asset) |
||||
{ |
||||
foreach ($this->filters as $filter) { |
||||
$filter->filterLoad($asset); |
||||
} |
||||
} |
||||
|
||||
public function filterDump(AssetInterface $asset) |
||||
{ |
||||
foreach ($this->filters as $filter) { |
||||
$filter->filterDump($asset); |
||||
} |
||||
} |
||||
|
||||
public function getIterator() |
||||
{ |
||||
return new \ArrayIterator($this->filters); |
||||
} |
||||
|
||||
public function count() |
||||
{ |
||||
return count($this->filters); |
||||
} |
||||
} |
||||
@ -0,0 +1,36 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Filter; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
|
||||
/** |
||||
* A filter manipulates an asset at load and dump. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
interface FilterInterface |
||||
{ |
||||
/** |
||||
* Filters an asset after it has been loaded. |
||||
* |
||||
* @param AssetInterface $asset An asset |
||||
*/ |
||||
public function filterLoad(AssetInterface $asset); |
||||
|
||||
/** |
||||
* Filters an asset just before it's dumped. |
||||
* |
||||
* @param AssetInterface $asset An asset |
||||
*/ |
||||
public function filterDump(AssetInterface $asset); |
||||
} |
||||
@ -0,0 +1,84 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Filter\GoogleClosure; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
use Assetic\Filter\FilterInterface; |
||||
|
||||
/** |
||||
* Base filter for the Google Closure Compiler implementations. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
abstract class BaseCompilerFilter implements FilterInterface |
||||
{ |
||||
// compilation levels |
||||
const COMPILE_WHITESPACE_ONLY = 'WHITESPACE_ONLY'; |
||||
const COMPILE_SIMPLE_OPTIMIZATIONS = 'SIMPLE_OPTIMIZATIONS'; |
||||
const COMPILE_ADVANCED_OPTIMIZATIONS = 'ADVANCED_OPTIMIZATIONS'; |
||||
|
||||
// formatting modes |
||||
const FORMAT_PRETTY_PRINT = 'pretty_print'; |
||||
const FORMAT_PRINT_INPUT_DELIMITER = 'print_input_delimiter'; |
||||
|
||||
// warning levels |
||||
const LEVEL_QUIET = 'QUIET'; |
||||
const LEVEL_DEFAULT = 'DEFAULT'; |
||||
const LEVEL_VERBOSE = 'VERBOSE'; |
||||
|
||||
protected $compilationLevel; |
||||
protected $jsExterns; |
||||
protected $externsUrl; |
||||
protected $excludeDefaultExterns; |
||||
protected $formatting; |
||||
protected $useClosureLibrary; |
||||
protected $warningLevel; |
||||
|
||||
public function setCompilationLevel($compilationLevel) |
||||
{ |
||||
$this->compilationLevel = $compilationLevel; |
||||
} |
||||
|
||||
public function setJsExterns($jsExterns) |
||||
{ |
||||
$this->jsExterns = $jsExterns; |
||||
} |
||||
|
||||
public function setExternsUrl($externsUrl) |
||||
{ |
||||
$this->externsUrl = $externsUrl; |
||||
} |
||||
|
||||
public function setExcludeDefaultExterns($excludeDefaultExterns) |
||||
{ |
||||
$this->excludeDefaultExterns = $excludeDefaultExterns; |
||||
} |
||||
|
||||
public function setFormatting($formatting) |
||||
{ |
||||
$this->formatting = $formatting; |
||||
} |
||||
|
||||
public function setUseClosureLibrary($useClosureLibrary) |
||||
{ |
||||
$this->useClosureLibrary = $useClosureLibrary; |
||||
} |
||||
|
||||
public function setWarningLevel($warningLevel) |
||||
{ |
||||
$this->warningLevel = $warningLevel; |
||||
} |
||||
|
||||
public function filterLoad(AssetInterface $asset) |
||||
{ |
||||
} |
||||
} |
||||
@ -0,0 +1,82 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Filter\GoogleClosure; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
|
||||
/** |
||||
* Filter for the Google Closure Compiler API. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class CompilerApiFilter extends BaseCompilerFilter |
||||
{ |
||||
public function filterDump(AssetInterface $asset) |
||||
{ |
||||
$query = array( |
||||
'js_code' => $asset->getContent(), |
||||
'output_format' => 'json', |
||||
'output_info' => 'compiled_code', |
||||
); |
||||
|
||||
if (null !== $this->compilationLevel) { |
||||
$query['compilation_level'] = $this->compilationLevel; |
||||
} |
||||
|
||||
if (null !== $this->jsExterns) { |
||||
$query['js_externs'] = $this->jsExterns; |
||||
} |
||||
|
||||
if (null !== $this->externsUrl) { |
||||
$query['externs_url'] = $this->externsUrl; |
||||
} |
||||
|
||||
if (null !== $this->excludeDefaultExterns) { |
||||
$query['exclude_default_externs'] = $this->excludeDefaultExterns ? 'true' : 'false'; |
||||
} |
||||
|
||||
if (null !== $this->formatting) { |
||||
$query['formatting'] = $this->formatting; |
||||
} |
||||
|
||||
if (null !== $this->useClosureLibrary) { |
||||
$query['use_closure_library'] = $this->useClosureLibrary ? 'true' : 'false'; |
||||
} |
||||
|
||||
if (null !== $this->warningLevel) { |
||||
$query['warning_level'] = $this->warningLevel; |
||||
} |
||||
|
||||
$context = stream_context_create(array('http' => array( |
||||
'method' => 'POST', |
||||
'header' => 'Content-Type: application/x-www-form-urlencoded', |
||||
'content' => http_build_query($query), |
||||
))); |
||||
|
||||
$response = file_get_contents('http://closure-compiler.appspot.com/compile', false, $context); |
||||
$data = json_decode($response); |
||||
|
||||
if (isset($data->serverErrors) && 0 < count($data->serverErrors)) { |
||||
// @codeCoverageIgnoreStart |
||||
throw new \RuntimeException(sprintf('The Google Closure Compiler API threw some server errors: '.print_r($data->serverErrors, true))); |
||||
// @codeCoverageIgnoreEnd |
||||
} |
||||
|
||||
if (isset($data->errors) && 0 < count($data->errors)) { |
||||
// @codeCoverageIgnoreStart |
||||
throw new \RuntimeException(sprintf('The Google Closure Compiler API threw some errors: '.print_r($data->errors, true))); |
||||
// @codeCoverageIgnoreEnd |
||||
} |
||||
|
||||
$asset->setContent($data->compiledCode); |
||||
} |
||||
} |
||||
@ -0,0 +1,88 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Filter\GoogleClosure; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
use Assetic\Util\ProcessBuilder; |
||||
|
||||
/** |
||||
* Filter for the Google Closure Compiler JAR. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class CompilerJarFilter extends BaseCompilerFilter |
||||
{ |
||||
private $jarPath; |
||||
private $javaPath; |
||||
|
||||
public function __construct($jarPath, $javaPath = '/usr/bin/java') |
||||
{ |
||||
$this->jarPath = $jarPath; |
||||
$this->javaPath = $javaPath; |
||||
} |
||||
|
||||
public function filterDump(AssetInterface $asset) |
||||
{ |
||||
$cleanup = array(); |
||||
|
||||
$pb = new ProcessBuilder(array( |
||||
$this->javaPath, |
||||
'-jar', |
||||
$this->jarPath, |
||||
)); |
||||
|
||||
if (null !== $this->compilationLevel) { |
||||
$pb->add('--compilation_level')->add($this->compilationLevel); |
||||
} |
||||
|
||||
if (null !== $this->jsExterns) { |
||||
$cleanup[] = $externs = tempnam(sys_get_temp_dir(), 'assetic_google_closure_compiler'); |
||||
file_put_contents($externs, $this->jsExterns); |
||||
$pb->add('--externs')->add($externs); |
||||
} |
||||
|
||||
if (null !== $this->externsUrl) { |
||||
$cleanup[] = $externs = tempnam(sys_get_temp_dir(), 'assetic_google_closure_compiler'); |
||||
file_put_contents($externs, file_get_contents($this->externsUrl)); |
||||
$pb->add('--externs')->add($externs); |
||||
} |
||||
|
||||
if (null !== $this->excludeDefaultExterns) { |
||||
$pb->add('--use_only_custom_externs'); |
||||
} |
||||
|
||||
if (null !== $this->formatting) { |
||||
$pb->add('--formatting')->add($this->formatting); |
||||
} |
||||
|
||||
if (null !== $this->useClosureLibrary) { |
||||
$pb->add('--manage_closure_dependencies'); |
||||
} |
||||
|
||||
if (null !== $this->warningLevel) { |
||||
$pb->add('--warning_level')->add($this->warningLevel); |
||||
} |
||||
|
||||
$pb->add('--js')->add($cleanup[] = $input = tempnam(sys_get_temp_dir(), 'assetic_google_closure_compiler')); |
||||
file_put_contents($input, $asset->getContent()); |
||||
|
||||
$proc = $pb->getProcess(); |
||||
$code = $proc->run(); |
||||
array_map('unlink', $cleanup); |
||||
|
||||
if (0 < $code) { |
||||
throw new \RuntimeException($proc->getErrorOutput()); |
||||
} |
||||
|
||||
$asset->setContent($proc->getOutput()); |
||||
} |
||||
} |
||||
@ -0,0 +1,27 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Filter; |
||||
|
||||
/** |
||||
* A filter can implement a hash function |
||||
* |
||||
* @author Francisco Facioni <fran6co@gmail.com> |
||||
*/ |
||||
interface HashableInterface |
||||
{ |
||||
/** |
||||
* Generates a hash for the object |
||||
* |
||||
* @return string Object hash |
||||
*/ |
||||
public function hash(); |
||||
} |
||||
@ -0,0 +1,80 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Filter; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
use Assetic\Util\ProcessBuilder; |
||||
|
||||
/** |
||||
* Runs assets through Jpegoptim. |
||||
* |
||||
* @link http://www.kokkonen.net/tjko/projects.html |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class JpegoptimFilter implements FilterInterface |
||||
{ |
||||
private $jpegoptimBin; |
||||
private $stripAll; |
||||
private $max; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param string $jpegoptimBin Path to the jpegoptim binary |
||||
*/ |
||||
public function __construct($jpegoptimBin = '/usr/bin/jpegoptim') |
||||
{ |
||||
$this->jpegoptimBin = $jpegoptimBin; |
||||
} |
||||
|
||||
public function setStripAll($stripAll) |
||||
{ |
||||
$this->stripAll = $stripAll; |
||||
} |
||||
|
||||
public function setMax($max) |
||||
{ |
||||
$this->max = $max; |
||||
} |
||||
|
||||
public function filterLoad(AssetInterface $asset) |
||||
{ |
||||
} |
||||
|
||||
public function filterDump(AssetInterface $asset) |
||||
{ |
||||
$pb = new ProcessBuilder(array($this->jpegoptimBin)); |
||||
|
||||
if ($this->stripAll) { |
||||
$pb->add('--strip-all'); |
||||
} |
||||
|
||||
if ($this->max) { |
||||
$pb->add('--max='.$this->max); |
||||
} |
||||
|
||||
$pb->add($input = tempnam(sys_get_temp_dir(), 'assetic_jpegoptim')); |
||||
file_put_contents($input, $asset->getContent()); |
||||
|
||||
$proc = $pb->getProcess(); |
||||
$proc->run(); |
||||
|
||||
if (false !== strpos($proc->getOutput(), 'ERROR')) { |
||||
unlink($input); |
||||
throw new \RuntimeException($proc->getOutput()); |
||||
} |
||||
|
||||
$asset->setContent(file_get_contents($input)); |
||||
|
||||
unlink($input); |
||||
} |
||||
} |
||||
@ -0,0 +1,101 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Filter; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
use Assetic\Util\ProcessBuilder; |
||||
|
||||
/** |
||||
* Runs assets through jpegtran. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class JpegtranFilter implements FilterInterface |
||||
{ |
||||
const COPY_NONE = 'none'; |
||||
const COPY_COMMENTS = 'comments'; |
||||
const COPY_ALL = 'all'; |
||||
|
||||
private $jpegtranBin; |
||||
private $optimize; |
||||
private $copy; |
||||
private $progressive; |
||||
private $restart; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param string $jpegtranBin Path to the jpegtran binary |
||||
*/ |
||||
public function __construct($jpegtranBin = '/usr/bin/jpegtran') |
||||
{ |
||||
$this->jpegtranBin = $jpegtranBin; |
||||
} |
||||
|
||||
public function setOptimize($optimize) |
||||
{ |
||||
$this->optimize = $optimize; |
||||
} |
||||
|
||||
public function setCopy($copy) |
||||
{ |
||||
$this->copy = $copy; |
||||
} |
||||
|
||||
public function setProgressive($progressive) |
||||
{ |
||||
$this->progressive = $progressive; |
||||
} |
||||
|
||||
public function setRestart($restart) |
||||
{ |
||||
$this->restart = $restart; |
||||
} |
||||
|
||||
public function filterLoad(AssetInterface $asset) |
||||
{ |
||||
} |
||||
|
||||
public function filterDump(AssetInterface $asset) |
||||
{ |
||||
$pb = new ProcessBuilder(array($this->jpegtranBin)); |
||||
|
||||
if ($this->optimize) { |
||||
$pb->add('-optimize'); |
||||
} |
||||
|
||||
if ($this->copy) { |
||||
$pb->add('-copy')->add($this->copy); |
||||
} |
||||
|
||||
if ($this->progressive) { |
||||
$pb->add('-progressive'); |
||||
} |
||||
|
||||
if (null !== $this->restart) { |
||||
$pb->add('-restart')->add($this->restart); |
||||
} |
||||
|
||||
$pb->add($input = tempnam(sys_get_temp_dir(), 'assetic_jpegtran')); |
||||
file_put_contents($input, $asset->getContent()); |
||||
|
||||
$proc = $pb->getProcess(); |
||||
$code = $proc->run(); |
||||
unlink($input); |
||||
|
||||
if (0 < $code) { |
||||
throw new \RuntimeException($proc->getErrorOutput()); |
||||
} |
||||
|
||||
$asset->setContent($proc->getOutput()); |
||||
} |
||||
} |
||||
@ -0,0 +1,112 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Filter; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
use Assetic\Util\ProcessBuilder; |
||||
|
||||
/** |
||||
* Loads LESS files. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class LessFilter implements FilterInterface |
||||
{ |
||||
private $nodeBin; |
||||
private $nodePaths; |
||||
private $compress; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param string $nodeBin The path to the node binary |
||||
* @param array $nodePaths An array of node paths |
||||
*/ |
||||
public function __construct($nodeBin = '/usr/bin/node', array $nodePaths = array()) |
||||
{ |
||||
$this->nodeBin = $nodeBin; |
||||
$this->nodePaths = $nodePaths; |
||||
} |
||||
|
||||
public function setCompress($compress) |
||||
{ |
||||
$this->compress = $compress; |
||||
} |
||||
|
||||
public function filterLoad(AssetInterface $asset) |
||||
{ |
||||
static $format = <<<'EOF' |
||||
var less = require('less'); |
||||
var sys = require(process.binding('natives').util ? 'util' : 'sys'); |
||||
|
||||
new(less.Parser)(%s).parse(%s, function(e, tree) { |
||||
if (e) { |
||||
less.writeError(e); |
||||
process.exit(2); |
||||
} |
||||
|
||||
try { |
||||
sys.print(tree.toCSS(%s)); |
||||
} catch (e) { |
||||
less.writeError(e); |
||||
process.exit(3); |
||||
} |
||||
}); |
||||
|
||||
EOF; |
||||
|
||||
$root = $asset->getSourceRoot(); |
||||
$path = $asset->getSourcePath(); |
||||
|
||||
// parser options |
||||
$parserOptions = array(); |
||||
if ($root && $path) { |
||||
$parserOptions['paths'] = array(dirname($root.'/'.$path)); |
||||
$parserOptions['filename'] = basename($path); |
||||
} |
||||
|
||||
// tree options |
||||
$treeOptions = array(); |
||||
if (null !== $this->compress) { |
||||
$treeOptions['compress'] = $this->compress; |
||||
} |
||||
|
||||
$pb = new ProcessBuilder(); |
||||
$pb->inheritEnvironmentVariables(); |
||||
|
||||
// node.js configuration |
||||
if (0 < count($this->nodePaths)) { |
||||
$pb->setEnv('NODE_PATH', implode(':', $this->nodePaths)); |
||||
} |
||||
|
||||
$pb->add($this->nodeBin)->add($input = tempnam(sys_get_temp_dir(), 'assetic_less')); |
||||
file_put_contents($input, sprintf($format, |
||||
json_encode($parserOptions), |
||||
json_encode($asset->getContent()), |
||||
json_encode($treeOptions) |
||||
)); |
||||
|
||||
$proc = $pb->getProcess(); |
||||
$code = $proc->run(); |
||||
unlink($input); |
||||
|
||||
if (0 < $code) { |
||||
throw new \RuntimeException($proc->getErrorOutput()); |
||||
} |
||||
|
||||
$asset->setContent($proc->getOutput()); |
||||
} |
||||
|
||||
public function filterDump(AssetInterface $asset) |
||||
{ |
||||
} |
||||
} |
||||
@ -0,0 +1,48 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Filter; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
|
||||
/** |
||||
* Loads LESS files using the PHP implementation of less, lessphp. |
||||
* |
||||
* Less files are mostly compatible, but there are slight differences. |
||||
* |
||||
* To use this, you need to clone https://github.com/leafo/lessphp and make |
||||
* sure to either include lessphp.inc.php or tell your autoloader that's where |
||||
* lessc is located. |
||||
* |
||||
* @link http://leafo.net/lessphp/ |
||||
* |
||||
* @author David Buchmann <david@liip.ch> |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class LessphpFilter implements FilterInterface |
||||
{ |
||||
public function filterLoad(AssetInterface $asset) |
||||
{ |
||||
$root = $asset->getSourceRoot(); |
||||
$path = $asset->getSourcePath(); |
||||
|
||||
$lc = new \lessc(); |
||||
if ($root && $path) { |
||||
$lc->importDir = dirname($root.'/'.$path); |
||||
} |
||||
|
||||
$asset->setContent($lc->parse($asset->getContent())); |
||||
} |
||||
|
||||
public function filterDump(AssetInterface $asset) |
||||
{ |
||||
} |
||||
} |
||||
@ -0,0 +1,74 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Filter; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
use Assetic\Util\ProcessBuilder; |
||||
|
||||
/** |
||||
* Runs assets through OptiPNG. |
||||
* |
||||
* @link http://optipng.sourceforge.net/ |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class OptiPngFilter implements FilterInterface |
||||
{ |
||||
private $optipngBin; |
||||
private $level; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param string $optipngBin Path to the optipng binary |
||||
*/ |
||||
public function __construct($optipngBin = '/usr/bin/optipng') |
||||
{ |
||||
$this->optipngBin = $optipngBin; |
||||
} |
||||
|
||||
public function setLevel($level) |
||||
{ |
||||
$this->level = $level; |
||||
} |
||||
|
||||
public function filterLoad(AssetInterface $asset) |
||||
{ |
||||
} |
||||
|
||||
public function filterDump(AssetInterface $asset) |
||||
{ |
||||
$pb = new ProcessBuilder(array($this->optipngBin)); |
||||
|
||||
if (null !== $this->level) { |
||||
$pb->add('-o')->add($this->level); |
||||
} |
||||
|
||||
$pb->add('-out')->add($output = tempnam(sys_get_temp_dir(), 'assetic_optipng')); |
||||
unlink($output); |
||||
|
||||
$pb->add($input = tempnam(sys_get_temp_dir(), 'assetic_optipng')); |
||||
file_put_contents($input, $asset->getContent()); |
||||
|
||||
$proc = $pb->getProcess(); |
||||
$code = $proc->run(); |
||||
|
||||
if (0 < $code) { |
||||
unlink($input); |
||||
throw new \RuntimeException($proc->getErrorOutput()); |
||||
} |
||||
|
||||
$asset->setContent(file_get_contents($output)); |
||||
|
||||
unlink($input); |
||||
unlink($output); |
||||
} |
||||
} |
||||
@ -0,0 +1,64 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Filter; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
|
||||
/** |
||||
* Runs assets through Packager. |
||||
* |
||||
* @link https://github.com/kamicane/packager |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class PackagerFilter implements FilterInterface |
||||
{ |
||||
private $packages; |
||||
|
||||
public function __construct(array $packages = array()) |
||||
{ |
||||
$this->packages = $packages; |
||||
} |
||||
|
||||
public function addPackage($package) |
||||
{ |
||||
$this->packages[] = $package; |
||||
} |
||||
|
||||
public function filterLoad(AssetInterface $asset) |
||||
{ |
||||
static $manifest = <<<EOF |
||||
name: Application%s |
||||
sources: [source.js] |
||||
|
||||
EOF; |
||||
|
||||
$hash = substr(sha1(time().rand(11111, 99999)), 0, 7); |
||||
$package = sys_get_temp_dir().'/assetic_packager_'.$hash; |
||||
|
||||
mkdir($package); |
||||
file_put_contents($package.'/package.yml', sprintf($manifest, $hash)); |
||||
file_put_contents($package.'/source.js', $asset->getContent()); |
||||
|
||||
$packager = new \Packager(array_merge(array($package), $this->packages)); |
||||
$content = $packager->build(array(), array(), array('Application'.$hash)); |
||||
|
||||
unlink($package.'/package.yml'); |
||||
unlink($package.'/source.js'); |
||||
rmdir($package); |
||||
|
||||
$asset->setContent($content); |
||||
} |
||||
|
||||
public function filterDump(AssetInterface $asset) |
||||
{ |
||||
} |
||||
} |
||||
@ -0,0 +1,126 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Filter; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
use Assetic\Util\ProcessBuilder; |
||||
|
||||
/** |
||||
* Runs assets through pngout. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class PngoutFilter implements FilterInterface |
||||
{ |
||||
// -c# |
||||
const COLOR_GREY = '0'; |
||||
const COLOR_RGB = '2'; |
||||
const COLOR_PAL = '3'; |
||||
const COLOR_GRAY_ALPHA = '4'; |
||||
const COLOR_RGB_ALPHA = '6'; |
||||
|
||||
// -f# |
||||
const FILTER_NONE = '0'; |
||||
const FILTER_X = '1'; |
||||
const FILTER_Y = '2'; |
||||
const FILTER_X_Y = '3'; |
||||
const FILTER_PAETH = '4'; |
||||
const FILTER_MIXED = '5'; |
||||
|
||||
// -s# |
||||
const STRATEGY_XTREME = '0'; |
||||
const STRATEGY_INTENSE = '1'; |
||||
const STRATEGY_LONGEST_MATCH = '2'; |
||||
const STRATEGY_HUFFMAN_ONLY = '3'; |
||||
const STRATEGY_UNCOMPRESSED = '4'; |
||||
|
||||
private $pngoutBin; |
||||
private $color; |
||||
private $filter; |
||||
private $strategy; |
||||
private $blockSplitThreshold; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param string $pngoutBin Path to the pngout binary |
||||
*/ |
||||
public function __construct($pngoutBin = '/usr/bin/pngout') |
||||
{ |
||||
$this->pngoutBin = $pngoutBin; |
||||
} |
||||
|
||||
public function setColor($color) |
||||
{ |
||||
$this->color = $color; |
||||
} |
||||
|
||||
public function setFilter($filter) |
||||
{ |
||||
$this->filter = $filter; |
||||
} |
||||
|
||||
public function setStrategy($strategy) |
||||
{ |
||||
$this->strategy = $strategy; |
||||
} |
||||
|
||||
public function setBlockSplitThreshold($blockSplitThreshold) |
||||
{ |
||||
$this->blockSplitThreshold = $blockSplitThreshold; |
||||
} |
||||
|
||||
public function filterLoad(AssetInterface $asset) |
||||
{ |
||||
} |
||||
|
||||
public function filterDump(AssetInterface $asset) |
||||
{ |
||||
$pb = new ProcessBuilder(array($this->pngoutBin)); |
||||
|
||||
if (null !== $this->color) { |
||||
$pb->add('-c'.$this->color); |
||||
} |
||||
|
||||
if (null !== $this->filter) { |
||||
$pb->add('-f'.$this->filter); |
||||
} |
||||
|
||||
if (null !== $this->strategy) { |
||||
$pb->add('-s'.$this->strategy); |
||||
} |
||||
|
||||
if (null !== $this->blockSplitThreshold) { |
||||
$pb->add('-b'.$this->blockSplitThreshold); |
||||
} |
||||
|
||||
$pb->add($input = tempnam(sys_get_temp_dir(), 'assetic_pngout')); |
||||
file_put_contents($input, $asset->getContent()); |
||||
|
||||
$output = tempnam(sys_get_temp_dir(), 'assetic_pngout'); |
||||
unlink($output); |
||||
$pb->add($output .= '.png'); |
||||
|
||||
$proc = $pb->getProcess(); |
||||
$code = $proc->run(); |
||||
|
||||
if (0 < $code) { |
||||
unlink($input); |
||||
throw new \RuntimeException($proc->getErrorOutput()); |
||||
} |
||||
|
||||
$asset->setContent(file_get_contents($output)); |
||||
|
||||
unlink($input); |
||||
unlink($output); |
||||
} |
||||
} |
||||
@ -0,0 +1,167 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Filter\Sass; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
use Assetic\Filter\FilterInterface; |
||||
use Assetic\Util\ProcessBuilder; |
||||
|
||||
/** |
||||
* Loads SASS files. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class SassFilter implements FilterInterface |
||||
{ |
||||
const STYLE_NESTED = 'nested'; |
||||
const STYLE_EXPANDED = 'expanded'; |
||||
const STYLE_COMPACT = 'compact'; |
||||
const STYLE_COMPRESSED = 'compressed'; |
||||
|
||||
private $sassPath; |
||||
private $unixNewlines; |
||||
private $scss; |
||||
private $style; |
||||
private $quiet; |
||||
private $debugInfo; |
||||
private $lineNumbers; |
||||
private $loadPaths = array(); |
||||
private $cacheLocation; |
||||
private $noCache; |
||||
private $compass; |
||||
|
||||
public function __construct($sassPath = '/usr/bin/sass') |
||||
{ |
||||
$this->sassPath = $sassPath; |
||||
$this->cacheLocation = realpath(sys_get_temp_dir()); |
||||
} |
||||
|
||||
public function setUnixNewlines($unixNewlines) |
||||
{ |
||||
$this->unixNewlines = $unixNewlines; |
||||
} |
||||
|
||||
public function setScss($scss) |
||||
{ |
||||
$this->scss = $scss; |
||||
} |
||||
|
||||
public function setStyle($style) |
||||
{ |
||||
$this->style = $style; |
||||
} |
||||
|
||||
public function setQuiet($quiet) |
||||
{ |
||||
$this->quiet = $quiet; |
||||
} |
||||
|
||||
public function setDebugInfo($debugInfo) |
||||
{ |
||||
$this->debugInfo = $debugInfo; |
||||
} |
||||
|
||||
public function setLineNumbers($lineNumbers) |
||||
{ |
||||
$this->lineNumbers = $lineNumbers; |
||||
} |
||||
|
||||
public function addLoadPath($loadPath) |
||||
{ |
||||
$this->loadPaths[] = $loadPath; |
||||
} |
||||
|
||||
public function setCacheLocation($cacheLocation) |
||||
{ |
||||
$this->cacheLocation = $cacheLocation; |
||||
} |
||||
|
||||
public function setNoCache($noCache) |
||||
{ |
||||
$this->noCache = $noCache; |
||||
} |
||||
|
||||
public function setCompass($compass) |
||||
{ |
||||
$this->compass = $compass; |
||||
} |
||||
|
||||
public function filterLoad(AssetInterface $asset) |
||||
{ |
||||
$pb = new ProcessBuilder(array($this->sassPath)); |
||||
|
||||
$root = $asset->getSourceRoot(); |
||||
$path = $asset->getSourcePath(); |
||||
|
||||
if ($root && $path) { |
||||
$pb->add('--load-path')->add(dirname($root.'/'.$path)); |
||||
} |
||||
|
||||
if ($this->unixNewlines) { |
||||
$pb->add('--unix-newlines'); |
||||
} |
||||
|
||||
if (true === $this->scss || (null === $this->scss && 'scss' == pathinfo($path, PATHINFO_EXTENSION))) { |
||||
$pb->add('--scss'); |
||||
} |
||||
|
||||
if ($this->style) { |
||||
$pb->add('--style')->add($this->style); |
||||
} |
||||
|
||||
if ($this->quiet) { |
||||
$pb->add('--quiet'); |
||||
} |
||||
|
||||
if ($this->debugInfo) { |
||||
$pb->add('--debug-info'); |
||||
} |
||||
|
||||
if ($this->lineNumbers) { |
||||
$pb->add('--line-numbers'); |
||||
} |
||||
|
||||
foreach ($this->loadPaths as $loadPath) { |
||||
$pb->add('--load-path')->add($loadPath); |
||||
} |
||||
|
||||
if ($this->cacheLocation) { |
||||
$pb->add('--cache-location')->add($this->cacheLocation); |
||||
} |
||||
|
||||
if ($this->noCache) { |
||||
$pb->add('--no-cache'); |
||||
} |
||||
|
||||
if ($this->compass) { |
||||
$pb->add('--compass'); |
||||
} |
||||
|
||||
// input |
||||
$pb->add($input = tempnam(sys_get_temp_dir(), 'assetic_sass')); |
||||
file_put_contents($input, $asset->getContent()); |
||||
|
||||
$proc = $pb->getProcess(); |
||||
$code = $proc->run(); |
||||
unlink($input); |
||||
|
||||
if (0 < $code) { |
||||
throw new \RuntimeException($proc->getErrorOutput()); |
||||
} |
||||
|
||||
$asset->setContent($proc->getOutput()); |
||||
} |
||||
|
||||
public function filterDump(AssetInterface $asset) |
||||
{ |
||||
} |
||||
} |
||||
@ -0,0 +1,27 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Filter\Sass; |
||||
|
||||
/** |
||||
* Loads SCSS files. |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class ScssFilter extends SassFilter |
||||
{ |
||||
public function __construct($sassPath = '/usr/bin/sass') |
||||
{ |
||||
parent::__construct($sassPath); |
||||
|
||||
$this->setScss(true); |
||||
} |
||||
} |
||||
@ -0,0 +1,147 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Filter; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
use Assetic\Util\ProcessBuilder; |
||||
|
||||
/** |
||||
* Runs assets through Sprockets. |
||||
* |
||||
* Requires Sprockets 1.0.x. |
||||
* |
||||
* @link http://getsprockets.org/ |
||||
* @link http://github.com/sstephenson/sprockets/tree/1.0.x |
||||
* |
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com> |
||||
*/ |
||||
class SprocketsFilter implements FilterInterface |
||||
{ |
||||
private $sprocketsLib; |
||||
private $rubyBin; |
||||
private $includeDirs; |
||||
private $assetRoot; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param string $sprocketsLib Path to the Sprockets lib/ directory |
||||
* @param string $rubyBin Path to the ruby binary |
||||
*/ |
||||
public function __construct($sprocketsLib = null, $rubyBin = '/usr/bin/ruby') |
||||
{ |
||||
$this->sprocketsLib = $sprocketsLib; |
||||
$this->rubyBin = $rubyBin; |
||||
$this->includeDirs = array(); |
||||
} |
||||
|
||||
public function addIncludeDir($directory) |
||||
{ |
||||
$this->includeDirs[] = $directory; |
||||
} |
||||
|
||||
public function setAssetRoot($assetRoot) |
||||
{ |
||||
$this->assetRoot = $assetRoot; |
||||
} |
||||
|
||||
/** |
||||
* Hack around a bit, get the job done. |
||||
*/ |
||||
public function filterLoad(AssetInterface $asset) |
||||
{ |
||||
static $format = <<<'EOF' |
||||
#!/usr/bin/env ruby |
||||
|
||||
require %s |
||||
%s |
||||
options = { :load_path => [], |
||||
:source_files => [%s], |
||||
:expand_paths => false } |
||||
|
||||
%ssecretary = Sprockets::Secretary.new(options) |
||||
secretary.install_assets if options[:asset_root] |
||||
print secretary.concatenation |
||||
|
||||
EOF; |
||||
|
||||
$more = ''; |
||||
|
||||
foreach ($this->includeDirs as $directory) { |
||||
$more .= 'options[:load_path] << '.var_export($directory, true)."\n"; |
||||
} |
||||
|
||||
if (null !== $this->assetRoot) { |
||||
$more .= 'options[:asset_root] = '.var_export($this->assetRoot, true)."\n"; |
||||
} |
||||
|
||||
if ($more) { |
||||
$more .= "\n"; |
||||
} |
||||
|
||||
$tmpAsset = tempnam(sys_get_temp_dir(), 'assetic_sprockets'); |
||||
file_put_contents($tmpAsset, $asset->getContent()); |
||||
|
||||
$input = tempnam(sys_get_temp_dir(), 'assetic_sprockets'); |
||||
file_put_contents($input, sprintf($format, |
||||
$this->sprocketsLib |
||||
? sprintf('File.join(%s, \'sprockets\')', var_export($this->sprocketsLib, true)) |
||||
: '\'sprockets\'', |
||||
$this->getHack($asset), |
||||
var_export($tmpAsset, true), |
||||
$more |
||||
)); |
||||
|
||||
$pb = new ProcessBuilder(array( |
||||
$this->rubyBin, |
||||
$input, |
||||
)); |
||||
|
||||
$proc = $pb->getProcess(); |
||||
$code = $proc->run(); |
||||
unlink($tmpAsset); |
||||
unlink($input); |
||||
|
||||
if (0 < $code) { |
||||
throw new \RuntimeException($proc->getErrorOutput()); |
||||
} |
||||
|
||||
$asset->setContent($proc->getOutput()); |
||||
} |
||||
|
||||
public function filterDump(AssetInterface $asset) |
||||
{ |
||||
} |
||||
|
||||
private function getHack(AssetInterface $asset) |
||||
{ |
||||
static $format = <<<'EOF' |
||||
|
||||
module Sprockets |
||||
class Preprocessor |
||||
protected |
||||
def pathname_for_relative_require_from(source_line) |
||||
Sprockets::Pathname.new(@environment, File.join(%s, location_from(source_line))) |
||||
end |
||||
end |
||||
end |
||||
|
||||
EOF; |
||||
|
||||
$root = $asset->getSourceRoot(); |
||||
$path = $asset->getSourcePath(); |
||||
|
||||
if ($root && $path) { |
||||
return sprintf($format, var_export(dirname($root.'/'.$path), true)); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,115 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the Assetic package, an OpenSky project. |
||||
* |
||||
* (c) 2010-2012 OpenSky Project Inc |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Assetic\Filter; |
||||
|
||||
use Assetic\Asset\AssetInterface; |
||||
use Assetic\Util\ProcessBuilder; |
||||
|
||||
/** |
||||
* Loads STYL files. |
||||
* |
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com> |
||||
*/ |
||||
class StylusFilter implements FilterInterface |
||||
{ |
||||
private $nodeBin; |
||||
private $nodePaths; |
||||
private $compress; |
||||
|
||||
/** |
||||
* Constructs filter. |
||||
* |
||||
* @param string $nodeBin The path to the node binary |
||||
* @param array $nodePaths An array of node paths |
||||
*/ |
||||
public function __construct($nodeBin = '/usr/bin/node', array $nodePaths = array()) |
||||
{ |
||||
$this->nodeBin = $nodeBin; |
||||
$this->nodePaths = $nodePaths; |
||||
} |
||||
|
||||
/** |
||||
* Enable output compression. |
||||
* |
||||
* @param boolean $compress |
||||
*/ |
||||
public function setCompress($compress) |
||||
{ |
||||
$this->compress = $compress; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function filterLoad(AssetInterface $asset) |
||||
{ |
||||
static $format = <<<'EOF' |
||||
var stylus = require('stylus'); |
||||
var sys = require(process.binding('natives').util ? 'util' : 'sys'); |
||||
|
||||
stylus(%s, %s).render(function(e, css){ |
||||
if (e) { |
||||
throw e; |
||||
} |
||||
|
||||
sys.print(css); |
||||
process.exit(0); |
||||
}); |
||||
|
||||
EOF; |
||||
|
||||
$root = $asset->getSourceRoot(); |
||||
$path = $asset->getSourcePath(); |
||||
|
||||
// parser options |
||||
$parserOptions = array(); |
||||
if ($root && $path) { |
||||
$parserOptions['paths'] = array(dirname($root.'/'.$path)); |
||||
$parserOptions['filename'] = basename($path); |
||||
} |
||||
|
||||
if (null !== $this->compress) { |
||||
$parserOptions['compress'] = $this->compress; |
||||
} |
||||
|
||||
$pb = new ProcessBuilder(); |
||||
$pb->inheritEnvironmentVariables(); |
||||
|
||||
// node.js configuration |
||||
if (0 < count($this->nodePaths)) { |
||||
$pb->setEnv('NODE_PATH', implode(':', $this->nodePaths)); |
||||
} |
||||
|
||||
$pb->add($this->nodeBin)->add($input = tempnam(sys_get_temp_dir(), 'assetic_stylus')); |
||||
file_put_contents($input, sprintf($format, |
||||
json_encode($asset->getContent()), |
||||
json_encode($parserOptions) |
||||
)); |
||||
|
||||
$proc = $pb->getProcess(); |
||||
$code = $proc->run(); |
||||
unlink($input); |
||||
|
||||
if (0 < $code) { |
||||
throw new \RuntimeException($proc->getErrorOutput()); |
||||
} |
||||
|
||||
$asset->setContent($proc->getOutput()); |
||||
} |
||||
|
||||
/** |
||||
* {@inheritdoc} |
||||
*/ |
||||
public function filterDump(AssetInterface $asset) |
||||
{ |
||||
} |
||||
} |
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue