Add upstream

This commit is contained in:
root
2019-10-24 00:12:05 +02:00
parent 85d41e4216
commit ac980f592c
3504 changed files with 1049983 additions and 29971 deletions

View File

@@ -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

View File

@@ -0,0 +1,57 @@
1.1.2 (July 18, 2013)
-------------------
* Fixed deep mtime on asset collections
* `CallablesFilter` now implements `DependencyExtractorInterface`
* Fixed detection of "partial" children in subfolders in `SassFilter`
* Restored `PathUtils` for BC
1.1.1 (June 1, 2013)
--------------------
* Fixed cloning of asset collections
* Fixed environment var inheritance
* Replaced `AssetWriter::getCombinations()` for BC, even though we don't use it
* Added support for `@import-once` to Less filters
1.1.0 (May 15, 2013)
--------------------
* Added LazyAssetManager::getLastModified() for determining "deep" mtime
* Added DartFilter
* Added EmberPrecompile
* Added GssFilter
* Added PhpCssEmbedFilter
* Added RooleFilter
* Added TypeScriptFilter
* Added the possibility to configure additional load paths for less and lessphp
* Added the UglifyCssFilter
* Fixed the handling of directories in the GlobAsset. #256
* Added Handlebars support
* Added Scssphp-compass support
* Added the CacheBustingWorker
* Added the UglifyJs2Filter
1.1.0-alpha1 (August 28, 2012)
------------------------------
* Added pure php css embed filter
* Added Scssphp support
* Added support for Google Closure language option
* Added a way to set a specific ruby path for CompassFilter and SassFilter
* Ensure uniqueness of temporary files created by the compressor filter. Fixed #61
* Added Compass option for generated_images_path (for generated Images/Sprites)
* Added PackerFilter
* Add the way to contact closure compiler API using curl, if available and allow_url_fopen is off
* Added filters for JSMin and JSMinPlus
* Added the UglifyJsFilter
* Improved the error message in getModifiedTime when a file asset uses an invalid file
* added support for asset variables:
Asset variables allow you to pre-compile your assets for a finite set of known
variable values, and then to simply deliver the correct asset version at runtime.
For example, this is helpful for assets with language, or browser-specific code.
* Removed the copy-paste of the Symfony2 Process component and use the original one
* Added ability to pass variables into lessphp filter
* Added google closure stylesheets jar filter
* Added the support of `--bare` for the CoffeeScriptFilter

View File

@@ -0,0 +1,49 @@
1.2.0 (2014-10-14)
------------------
### New features
* Added the autoprefixer filter
* Added --no-header option for Coffeescript
* Implemented the extraction of dependencies for the compass filter
* Allow custom functions to be registered on the Lessphp filter
* Added MinifyCssCompressor filter based on `mrclay/minify`
* Added `setVariables` in the ScssPhpFilter
* Improved the support of the compress options for UglifyJS2
* Added CssCacheBustingFilter to apply cache busting on URLs in the CSS
* Added support for `--relative-assets` option for the compass filter
### Bug fixes
* Allow functions.php to be included many times
* Updated the ScssPhpFilter for upstream class renaming
1.2.0-alpha1 (2014-07-08)
-------------------------
### BC breaks
* Added `AssetFactory` instance as second argument for `WorkerInterface::process()`
* Removed `LazyAssetManager` from `CacheBustingWorker` constructor
* A new `getSourceDirectory()` method was added on the AssetInterface
* Removed limit and count arguments from CssUtils functions
* Removed the deprecated `PathUtils` class
### New features
* added `CssUtils::filterCommentless()`
* Added `DependencyExtractorInterface` for filters to report other files they import
* Added the support of nib in the stylus filter
* Added `registerFunction` and `setFormatter` to the scssphp filter
* Added the support of flag file for the ClosureCompilerJarFilter
* Added the JsSqueeze filter
* Added support of the define option for uglifyjs (1 & 2) filters
* Added logging for Twig errors in the extractor
### Bug fixes
* Fixed the detection of protocol-relative CSS imports
* Updated AssetCollection to not add several time the same variable in path
* Fixed the merging of the environment variables to keep the configuration of the NODE_PATH working
* Fixed the support of ``{% embed %}`` in the Twig extractor
* Fixed the support of asset variables in GlobAsset

View File

@@ -0,0 +1,5 @@
source "https://rubygems.org"
gem "sprockets", "~> 1.0.0"
gem "sass"
gem "compass"

View File

@@ -0,0 +1,19 @@
Copyright (c) 2010-2015 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.

View File

@@ -0,0 +1,345 @@
# Assetic [![Build Status](https://travis-ci.org/kriswallsmith/assetic.png?branch=master)](https://travis-ci.org/kriswallsmith/assetic) ![project status](http://stillmaintained.com/kriswallsmith/assetic.png) #
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 |
The "target path" property denotes where an asset (or an collection of assets) should be dumped.
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:
* `AutoprefixerFilter`: Parse and update vendor-specific properties using autoprefixer
* `CoffeeScriptFilter`: compiles CoffeeScript into Javascript
* `CompassFilter`: Compass CSS authoring framework
* `CssEmbedFilter`: embeds image data in your stylesheets
* `CssImportFilter`: inlines imported stylesheets
* `CssMinFilter`: minifies CSS
* `CleanCssFilter`: minifies CSS
* `CssRewriteFilter`: fixes relative URLs in CSS assets when moving to a new URL
* `DartFilter`: compiles Javascript using dart2js
* `EmberPrecompileFilter`: precompiles Handlebars templates into Javascript for use in the Ember.js framework
* `GoogleClosure\CompilerApiFilter`: compiles Javascript using the Google Closure Compiler API
* `GoogleClosure\CompilerJarFilter`: compiles Javascript using the Google Closure Compiler JAR
* `GssFilter`: compliles CSS using the Google Closure Stylesheets Compiler
* `HandlebarsFilter`: compiles Handlebars templates into Javascript
* `JpegoptimFilter`: optimize your JPEGs
* `JpegtranFilter`: optimize your JPEGs
* `JSMinFilter`: minifies Javascript
* `JSMinPlusFilter`: minifies Javascript
* `JSqueezeFilter`: compresses Javascript
* `LessFilter`: parses LESS into CSS (using less.js with node.js)
* `LessphpFilter`: parses LESS into CSS (using lessphp)
* `OptiPngFilter`: optimize your PNGs
* `PackagerFilter`: parses Javascript for packager tags
* `PackerFilter`: compresses Javascript using Dean Edwards's Packer
* `PhpCssEmbedFilter`: embeds image data in your stylesheet
* `PngoutFilter`: optimize your PNGs
* `ReactJsxFilter`: compiles React JSX into JavaScript
* `Sass\SassFilter`: parses SASS into CSS
* `Sass\ScssFilter`: parses SCSS into CSS
* `SassphpFilter`: parses Sass into CSS using the sassphp bindings for Libsass
* `ScssphpFilter`: parses SCSS using scssphp
* `SeparatorFilter`: inserts a separator between assets to prevent merge failures
* `SprocketsFilter`: Sprockets Javascript dependency management
* `StylusFilter`: parses STYL into CSS
* `TypeScriptFilter`: parses TypeScript into Javascript
* `UglifyCssFilter`: minifies CSS
* `UglifyJs2Filter`: minifies Javascript
* `UglifyJsFilter`: minifies Javascript
* `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();
```
The `AssetFactory` is constructed with a root directory which is used as the base directory for relative asset paths.
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.
You can also register [Workers](src/Assetic/Factory/Worker/WorkerInterface.php) on the factory and all assets created
by it will be passed to the worker's `process()` method before being returned. See _Cache Busting_ below for an example.
Dumping Assets to static files
------------------------------
You can dump all the assets an AssetManager holds to files in a directory. This will probably be below your webserver's document root
so the files can be served statically.
``` php
<?php
use Assetic\AssetWriter;
$writer = new AssetWriter('/path/to/web');
$writer->writeManagerAssets($am);
```
This will make use of the assets' target path.
Cache Busting
-------------
If you serve your assets from static files as just described, you can use the CacheBustingWorker to rewrite the target
paths for assets. It will insert an identifier before the filename extension that is unique for a particular version
of the asset.
This identifier is based on the modification time of the asset and will also take depended-on assets into
consideration if the applied filters support it.
``` php
<?php
use Assetic\Factory\AssetFactory;
use Assetic\Factory\Worker\CacheBustingWorker;
$factory = new AssetFactory('/path/to/asset/directory/');
$factory->setAssetManager($am);
$factory->setFilterManager($fm);
$factory->setDebug(true);
$factory->addWorker(new CacheBustingWorker());
$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();
```
Internal 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();
```
Twig
----
To use the Assetic [Twig][3] extension you must register it to your Twig
environment:
``` php
<?php
$twig->addExtension(new AsseticExtension($factory));
```
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

View File

@@ -0,0 +1,56 @@
{
"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",
"symfony/process": "~2.1|~3.0"
},
"conflict": {
"twig/twig": "<1.27"
},
"require-dev": {
"leafo/lessphp": "^0.3.7",
"leafo/scssphp": "~0.1",
"meenie/javascript-packer": "^1.1",
"mrclay/minify": "<2.3",
"natxet/cssmin": "3.0.4",
"patchwork/jsqueeze": "~1.0|~2.0",
"phpunit/phpunit": "~4.8 || ^5.6",
"psr/log": "~1.0",
"ptachoire/cssembed": "~1.0",
"symfony/phpunit-bridge": "~2.7|~3.0",
"twig/twig": "~1.23|~2.0",
"yfix/packager": "dev-master"
},
"suggest": {
"twig/twig": "Assetic provides the integration with the Twig templating engine",
"leafo/lessphp": "Assetic provides the integration with the lessphp LESS compiler",
"leafo/scssphp": "Assetic provides the integration with the scssphp SCSS compiler",
"ptachoire/cssembed": "Assetic provides the integration with phpcssembed to embed data uris",
"leafo/scssphp-compass": "Assetic provides the integration with the SCSS compass plugin",
"patchwork/jsqueeze": "Assetic provides the integration with the JSqueeze JavaScript compressor"
},
"autoload": {
"psr-0": { "Assetic": "src/" },
"files": [ "src/functions.php" ]
},
"config": {
"bin-dir": "bin"
},
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
}
}
}

View File

@@ -0,0 +1,19 @@
{
"devDependencies": {
"uglifycss": "*",
"coffee-script": "*",
"stylus": "*",
"nib": "*",
"ember-precompile": "*",
"typescript": "*",
"less": "*",
"handlebars": "*",
"uglify-js": "*",
"autoprefixer": "*",
"autoprefixer-5": "^1.x",
"autoprefixer-cli": "*",
"roole": "*",
"react-tools": "*",
"clean-css": "*"
}
}

View File

@@ -0,0 +1,174 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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 getSourceDirectory()
{
return $this->asset->getSourceDirectory();
}
public function getTargetPath()
{
return $this->asset->getTargetPath();
}
public function setTargetPath($targetPath)
{
$this->asset->setTargetPath($targetPath);
}
public function getLastModified()
{
return $this->asset->getLastModified();
}
public function getVars()
{
return $this->asset->getVars();
}
public function setValues(array $values)
{
$this->asset->setValues($values);
}
public function getValues()
{
return $this->asset->getValues();
}
/**
* 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);
}
}
if ($values = $asset->getValues()) {
asort($values);
$cacheKey .= serialize($values);
}
return md5($cacheKey.$salt);
}
}

View File

@@ -0,0 +1,238 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Asset\Iterator\AssetCollectionFilterIterator;
use Assetic\Asset\Iterator\AssetCollectionIterator;
use Assetic\Filter\FilterCollection;
use Assetic\Filter\FilterInterface;
/**
* A collection of assets.
*
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
*/
class AssetCollection implements \IteratorAggregate, AssetCollectionInterface
{
private $assets;
private $filters;
private $sourceRoot;
private $targetPath;
private $content;
private $clones;
private $vars;
private $values;
/**
* Constructor.
*
* @param array $assets Assets for the current collection
* @param array $filters Filters for the current collection
* @param string $sourceRoot The root directory
* @param array $vars
*/
public function __construct($assets = array(), $filters = array(), $sourceRoot = null, array $vars = array())
{
$this->assets = array();
foreach ($assets as $asset) {
$this->add($asset);
}
$this->filters = new FilterCollection($filters);
$this->sourceRoot = $sourceRoot;
$this->clones = new \SplObjectStorage();
$this->vars = $vars;
$this->values = array();
}
public function __clone()
{
$this->filters = clone $this->filters;
$this->clones = new \SplObjectStorage();
}
public function all()
{
return $this->assets;
}
public function add(AssetInterface $asset)
{
$this->assets[] = $asset;
}
public function removeLeaf(AssetInterface $needle, $graceful = false)
{
foreach ($this->assets as $i => $asset) {
$clone = isset($this->clones[$asset]) ? $this->clones[$asset] : null;
if (in_array($needle, array($asset, $clone), true)) {
unset($this->clones[$asset], $this->assets[$i]);
return true;
}
if ($asset instanceof AssetCollectionInterface && $asset->removeLeaf($needle, true)) {
return true;
}
}
if ($graceful) {
return false;
}
throw new \InvalidArgumentException('Leaf not found.');
}
public function replaceLeaf(AssetInterface $needle, AssetInterface $replacement, $graceful = false)
{
foreach ($this->assets as $i => $asset) {
$clone = isset($this->clones[$asset]) ? $this->clones[$asset] : null;
if (in_array($needle, array($asset, $clone), true)) {
unset($this->clones[$asset]);
$this->assets[$i] = $replacement;
return true;
}
if ($asset instanceof AssetCollectionInterface && $asset->replaceLeaf($needle, $replacement, true)) {
return true;
}
}
if ($graceful) {
return false;
}
throw new \InvalidArgumentException('Leaf not found.');
}
public function ensureFilter(FilterInterface $filter)
{
$this->filters->ensure($filter);
}
public function getFilters()
{
return $this->filters->all();
}
public function clearFilters()
{
$this->filters->clear();
$this->clones = new \SplObjectStorage();
}
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 getSourceDirectory()
{
}
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;
}
$mtime = 0;
foreach ($this as $asset) {
$assetMtime = $asset->getLastModified();
if ($assetMtime > $mtime) {
$mtime = $assetMtime;
}
}
return $mtime;
}
/**
* Returns an iterator for looping recursively over unique leaves.
*/
public function getIterator()
{
return new \RecursiveIteratorIterator(new AssetCollectionFilterIterator(new AssetCollectionIterator($this, $this->clones)));
}
public function getVars()
{
return $this->vars;
}
public function setValues(array $values)
{
$this->values = $values;
foreach ($this as $asset) {
$asset->setValues(array_intersect_key($values, array_flip($asset->getVars())));
}
}
public function getValues()
{
return $this->values;
}
}

View File

@@ -0,0 +1,59 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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;
/**
* An asset collection.
*
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
*/
interface AssetCollectionInterface extends AssetInterface, \Traversable
{
/**
* Returns all child assets.
*
* @return array An array of AssetInterface objects
*/
public function all();
/**
* Adds an asset to the current collection.
*
* @param AssetInterface $asset An asset
*/
public function add(AssetInterface $asset);
/**
* Removes a leaf.
*
* @param AssetInterface $leaf The leaf to remove
* @param Boolean $graceful Whether the failure should return false or throw an exception
*
* @return Boolean Whether the asset has been found
*
* @throws \InvalidArgumentException If the asset cannot be found
*/
public function removeLeaf(AssetInterface $leaf, $graceful = false);
/**
* Replaces an existing leaf with a new one.
*
* @param AssetInterface $needle The current asset to replace
* @param AssetInterface $replacement The new asset
* @param Boolean $graceful Whether the failure should return false or throw an exception
*
* @return Boolean Whether the asset has been found
*
* @throws \InvalidArgumentException If the asset cannot be found
*/
public function replaceLeaf(AssetInterface $needle, AssetInterface $replacement, $graceful = false);
}

View File

@@ -0,0 +1,166 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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 asset's source directory.
*
* The source directory is the directory the asset was located in
* and can be used to resolve references relative to an asset.
*
* @return string|null The asset's source directory
*/
public function getSourceDirectory();
/**
* 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();
/**
* Returns an array of variable names for this asset.
*
* @return array
*/
public function getVars();
/**
* Sets the values for the asset's variables.
*
* @param array $values
*/
public function setValues(array $values);
/**
* Returns the current values for this asset.
*
* @return array an array of strings
*/
public function getValues();
}

View File

@@ -0,0 +1,164 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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();
private $clone = false;
private $asset;
public function __construct(AssetManager $am, $name)
{
$this->am = $am;
$this->name = $name;
}
public function __clone()
{
$this->clone = true;
if ($this->asset) {
$this->asset = clone $this->asset;
}
}
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 getSourceDirectory()
{
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__);
}
public function getVars()
{
return $this->callAsset(__FUNCTION__);
}
public function getValues()
{
return $this->callAsset(__FUNCTION__);
}
public function setValues(array $values)
{
$this->callAsset(__FUNCTION__, array($values));
}
// private
private function callAsset($method, $arguments = array())
{
$asset = $this->resolve();
return call_user_func_array(array($asset, $method), $arguments);
}
private function flushFilters()
{
$asset = $this->resolve();
while ($filter = array_shift($this->filters)) {
$asset->ensureFilter($filter);
}
}
private function resolve()
{
if ($this->asset) {
return $this->asset;
}
$asset = $this->am->get($this->name);
if ($this->clone) {
$asset = $this->asset = clone $asset;
}
return $asset;
}
}

View File

@@ -0,0 +1,181 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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 $sourceDir;
private $targetPath;
private $content;
private $loaded;
private $vars;
private $values;
/**
* Constructor.
*
* @param array $filters Filters for the asset
* @param string $sourceRoot The root directory
* @param string $sourcePath The asset path
* @param array $vars
*/
public function __construct($filters = array(), $sourceRoot = null, $sourcePath = null, array $vars = array())
{
$this->filters = new FilterCollection($filters);
$this->sourceRoot = $sourceRoot;
$this->sourcePath = $sourcePath;
if ($sourcePath && $sourceRoot) {
$this->sourceDir = dirname("$sourceRoot/$sourcePath");
}
$this->vars = $vars;
$this->values = array();
$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 getSourceDirectory()
{
return $this->sourceDir;
}
public function getTargetPath()
{
return $this->targetPath;
}
public function setTargetPath($targetPath)
{
if ($this->vars) {
foreach ($this->vars as $var) {
if (false === strpos($targetPath, $var)) {
throw new \RuntimeException(sprintf('The asset target path "%s" must contain the variable "{%s}".', $targetPath, $var));
}
}
}
$this->targetPath = $targetPath;
}
public function getVars()
{
return $this->vars;
}
public function setValues(array $values)
{
foreach ($values as $var => $v) {
if (!in_array($var, $this->vars, true)) {
throw new \InvalidArgumentException(sprintf('The asset with source path "%s" has no variable named "%s".', $this->sourcePath, $var));
}
}
$this->values = $values;
$this->loaded = false;
}
public function getValues()
{
return $this->values;
}
}

View File

@@ -0,0 +1,78 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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;
use Assetic\Util\VarUtils;
/**
* 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
* @param array $vars
*
* @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, array $vars = array())
{
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, $vars);
}
public function load(FilterInterface $additionalFilter = null)
{
$source = VarUtils::resolve($this->source, $this->getVars(), $this->getValues());
if (!is_file($source)) {
throw new \RuntimeException(sprintf('The source file "%s" does not exist.', $source));
}
$this->doLoad(file_get_contents($source), $additionalFilter);
}
public function getLastModified()
{
$source = VarUtils::resolve($this->source, $this->getVars(), $this->getValues());
if (!is_file($source)) {
throw new \RuntimeException(sprintf('The source file "%s" does not exist.', $source));
}
return filemtime($source);
}
}

View File

@@ -0,0 +1,115 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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;
use Assetic\Util\VarUtils;
/**
* 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
* @param array $vars
*/
public function __construct($globs, $filters = array(), $root = null, array $vars = array())
{
$this->globs = (array) $globs;
$this->initialized = false;
parent::__construct(array(), $filters, $root, $vars);
}
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();
}
public function setValues(array $values)
{
parent::setValues($values);
$this->initialized = false;
}
/**
* Initializes the collection based on the glob(s) passed in.
*/
private function initialize()
{
foreach ($this->globs as $glob) {
$glob = VarUtils::resolve($glob, $this->getVars(), $this->getValues());
if (false !== $paths = glob($glob)) {
foreach ($paths as $path) {
if (is_file($path)) {
$asset = new FileAsset($path, array(), $this->getSourceRoot(), null, $this->getVars());
$asset->setValues($this->getValues());
$this->add($asset);
}
}
}
}
$this->initialized = true;
}
}

View File

@@ -0,0 +1,79 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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;
use Assetic\Util\VarUtils;
/**
* 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
* @param Boolean $ignoreErrors
* @param array $vars
*
* @throws \InvalidArgumentException If the first argument is not an URL
*/
public function __construct($sourceUrl, $filters = array(), $ignoreErrors = false, array $vars = array())
{
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, $vars);
}
public function load(FilterInterface $additionalFilter = null)
{
$content = @file_get_contents(
VarUtils::resolve($this->sourceUrl, $this->getVars(), $this->getValues())
);
if (false === $content && !$this->ignoreErrors) {
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));
}
}
}
}
}

View File

@@ -0,0 +1,84 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Iterator;
/**
* 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>
*/
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);
}
}

View File

@@ -0,0 +1,128 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Iterator;
use Assetic\Asset\AssetCollectionInterface;
/**
* 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>
*/
class AssetCollectionIterator implements \RecursiveIterator
{
private $assets;
private $filters;
private $vars;
private $output;
private $clones;
public function __construct(AssetCollectionInterface $coll, \SplObjectStorage $clones)
{
$this->assets = $coll->all();
$this->filters = $coll->getFilters();
$this->vars = $coll->getVars();
$this->output = $coll->getTargetPath();
$this->clones = $clones;
if (false === $pos = strrpos($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
*
* @return \Assetic\Asset\AssetInterface
*/
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);
$name = $this->removeDuplicateVar($name);
$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 AssetCollectionInterface;
}
/**
* @uses current()
*/
public function getChildren()
{
return new self($this->current(), $this->clones);
}
private function removeDuplicateVar($name)
{
foreach ($this->vars as $var) {
$var = '{'.$var.'}';
if (false !== strpos($name, $var) && false !== strpos($this->output, $var)) {
$name = str_replace($var, '', $name);
}
}
return $name;
}
}

View File

@@ -0,0 +1,55 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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 $string;
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->string = $content;
parent::__construct($filters, $sourceRoot, $sourcePath);
}
public function load(FilterInterface $additionalFilter = null)
{
$this->doLoad($this->string, $additionalFilter);
}
public function setLastModified($lastModified)
{
$this->lastModified = $lastModified;
}
public function getLastModified()
{
return $this->lastModified;
}
}

View File

@@ -0,0 +1,89 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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
*
* @throws \InvalidArgumentException If the asset name is invalid
*/
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);
}
/**
* Clears all assets.
*/
public function clear()
{
$this->assets = array();
}
}

View File

@@ -0,0 +1,94 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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;
use Assetic\Util\VarUtils;
/**
* Writes assets to the filesystem.
*
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class AssetWriter
{
private $dir;
private $values;
/**
* Constructor.
*
* @param string $dir The base web directory
* @param array $values Variable values
*
* @throws \InvalidArgumentException if a variable value is not a string
*/
public function __construct($dir, array $values = array())
{
foreach ($values as $var => $vals) {
foreach ($vals as $value) {
if (!is_string($value)) {
throw new \InvalidArgumentException(sprintf('All variable values must be strings, but got %s for variable "%s".', json_encode($value), $var));
}
}
}
$this->dir = $dir;
$this->values = $values;
}
public function writeManagerAssets(AssetManager $am)
{
foreach ($am->getNames() as $name) {
$this->writeAsset($am->get($name));
}
}
public function writeAsset(AssetInterface $asset)
{
foreach (VarUtils::getCombinations($asset->getVars(), $this->values) as $combination) {
$asset->setValues($combination);
static::write(
$this->dir.'/'.VarUtils::resolve(
$asset->getTargetPath(),
$asset->getVars(),
$asset->getValues()
),
$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);
}
}
/**
* Not used.
*
* This method is provided for backward compatibility with certain versions
* of AsseticBundle.
*/
private function getCombinations(array $vars)
{
return VarUtils::getCombinations($vars, $this->values);
}
}

View File

@@ -0,0 +1,66 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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;
/**
* Uses APC to cache files
*
* @author André Roaldseth <andre@roaldseth.net>
*/
class ApcCache implements CacheInterface
{
public $ttl = 0;
/**
* @see CacheInterface::has()
*/
public function has($key)
{
return apc_exists($key);
}
/**
* @see CacheInterface::get()
*/
public function get($key)
{
$value = apc_fetch($key, $success);
if (!$success) {
throw new \RuntimeException('There is no cached value for '.$key);
}
return $value;
}
/**
* @see CacheInterface::set()
*/
public function set($key, $value)
{
$store = apc_store($key, $value, $this->ttl);
if (!$store) {
throw new \RuntimeException('Unable to store "'.$key.'" for '.$this->ttl.' seconds.');
}
return $store;
}
/**
* @see CacheInterface::remove()
*/
public function remove($key)
{
return apc_delete($key);
}
}

View File

@@ -0,0 +1,58 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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 array cache
*
* @author Michael Mifsud <xzyfer@gmail.com>
*/
class ArrayCache implements CacheInterface
{
private $cache = array();
/**
* @see CacheInterface::has()
*/
public function has($key)
{
return isset($this->cache[$key]);
}
/**
* @see CacheInterface::get()
*/
public function get($key)
{
if (!$this->has($key)) {
throw new \RuntimeException('There is no cached value for '.$key);
}
return $this->cache[$key];
}
/**
* @see CacheInterface::set()
*/
public function set($key, $value)
{
$this->cache[$key] = $value;
}
/**
* @see CacheInterface::remove()
*/
public function remove($key)
{
unset($this->cache[$key]);
}
}

View File

@@ -0,0 +1,53 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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);
}

View File

@@ -0,0 +1,123 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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';
}
}

View File

@@ -0,0 +1,60 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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);
}
}

View File

@@ -0,0 +1,65 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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);
}
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Assetic\Exception;
/**
* Marker.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface Exception
{
}

View File

@@ -0,0 +1,73 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 OpenSky Project Inc
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Assetic\Exception;
use Symfony\Component\Process\Process;
/**
* Describes an exception that occurred within a filter.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class FilterException extends \RuntimeException implements Exception
{
private $originalMessage;
private $input;
public static function fromProcess(Process $proc)
{
$message = sprintf("An error occurred while running:\n%s", $proc->getCommandLine());
$errorOutput = $proc->getErrorOutput();
if (!empty($errorOutput)) {
$message .= "\n\nError Output:\n".str_replace("\r", '', $errorOutput);
}
$output = $proc->getOutput();
if (!empty($output)) {
$message .= "\n\nOutput:\n".str_replace("\r", '', $output);
}
return new self($message);
}
public function __construct($message, $code = 0, \Exception $previous = null)
{
parent::__construct($message, $code, $previous);
$this->originalMessage = $message;
}
public function setInput($input)
{
$this->input = $input;
$this->updateMessage();
return $this;
}
public function getInput()
{
return $this->input;
}
private function updateMessage()
{
$message = $this->originalMessage;
if (!empty($this->input)) {
$message .= "\n\nInput:\n".$this->input;
}
$this->message = $message;
}
}

View File

@@ -0,0 +1,76 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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;
use Assetic\ValueSupplierInterface;
class AsseticExtension extends \Twig_Extension implements \Twig_Extension_GlobalsInterface
{
protected $factory;
protected $functions;
protected $valueSupplier;
public function __construct(AssetFactory $factory, $functions = array(), ValueSupplierInterface $valueSupplier = null)
{
$this->factory = $factory;
$this->functions = array();
$this->valueSupplier = $valueSupplier;
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[] = new AsseticFilterFunction($function);
}
return $functions;
}
public function getGlobals()
{
return array(
'assetic' => array(
'debug' => $this->factory->isDebug(),
'vars' => null !== $this->valueSupplier ? new ValueContainer($this->valueSupplier) : array(),
),
);
}
public function getFilterInvoker($function)
{
return new AsseticFilterInvoker($this->factory, $this->functions[$function]);
}
public function getName()
{
return 'assetic';
}
}

View File

@@ -0,0 +1,24 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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_SimpleFunction
{
public function __construct($name, $options = array())
{
parent::__construct($name, null, array_merge($options, array(
'needs_environment' => false,
'needs_context' => false,
'node_class' => '\Assetic\Extension\Twig\AsseticFilterNode',
)));
}
}

View File

@@ -0,0 +1,59 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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();
}
}

View File

@@ -0,0 +1,22 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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 AsseticFilterNode extends \Twig_Node_Expression_Function
{
protected function compileCallable(\Twig_Compiler $compiler)
{
$compiler->raw(sprintf('$this->env->getExtension(\'Assetic\\Extension\\Twig\\AsseticExtension\')->getFilterInvoker(\'%s\')->invoke', $this->getAttribute('name')));
$this->compileArguments($compiler);
}
}

View File

@@ -0,0 +1,165 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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_Node $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_Node $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)
{
if ($vars = $asset->getVars()) {
$compiler->write("// check variable conditions\n");
foreach ($vars as $var) {
$compiler
->write("if (!isset(\$context['assetic']['vars']['$var'])) {\n")
->indent()
->write("throw new \RuntimeException(sprintf('The asset \"".$name."\" expected variable \"".$var."\" to be set, but got only these vars: %s. Did you set-up a value supplier?', isset(\$context['assetic']['vars']) && \$context['assetic']['vars'] ? implode(', ', \$context['assetic']['vars']) : '# none #'));\n")
->outdent()
->write("}\n")
;
}
$compiler->raw("\n");
}
$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)
{
if (!$vars = $asset->getVars()) {
$compiler->repr($asset->getTargetPath());
return;
}
$compiler
->raw("strtr(")
->string($asset->getTargetPath())
->raw(", array(");
$first = true;
foreach ($vars as $var) {
if (!$first) {
$compiler->raw(", ");
}
$first = false;
$compiler
->string("{".$var."}")
->raw(" => \$context['assetic']['vars']['$var']")
;
}
$compiler
->raw("))")
;
}
}

View File

@@ -0,0 +1,198 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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',
'vars' => array(),
);
$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, 'vars')) {
// vars=['locale','browser']
$stream->next();
$stream->expect(\Twig_Token::OPERATOR_TYPE, '=');
$stream->expect(\Twig_Token::PUNCTUATION_TYPE, '[');
while ($stream->test(\Twig_Token::STRING_TYPE)) {
$attributes['vars'][] = $stream->expect(\Twig_Token::STRING_TYPE)->getValue();
if (!$stream->test(\Twig_Token::PUNCTUATION_TYPE, ',')) {
break;
}
$stream->next();
}
$stream->expect(\Twig_Token::PUNCTUATION_TYPE, ']');
} 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->getValue()), $token->getLine(), $stream->getFilename());
}
}
$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->createBodyNode($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()));
}
/**
* @param AssetInterface $asset
* @param \Twig_Node $body
* @param array $inputs
* @param array $filters
* @param string $name
* @param array $attributes
* @param int $lineno
* @param string $tag
*
* @return \Twig_Node
*/
protected function createBodyNode(AssetInterface $asset, \Twig_Node $body, array $inputs, array $filters, $name, array $attributes = array(), $lineno = 0, $tag = null)
{
$reflector = new \ReflectionMethod($this, 'createNode');
if (__CLASS__ !== $reflector->getDeclaringClass()->name) {
@trigger_error(sprintf('Overwriting %s::createNode is deprecated since 1.3. Overwrite %s instead.', __CLASS__, __METHOD__), E_USER_DEPRECATED);
return $this->createNode($asset, $body, $inputs, $filters, $name, $attributes, $lineno, $tag);
}
return new AsseticNode($asset, $body, $inputs, $filters, $name, $attributes, $lineno, $tag);
}
/**
* @param AssetInterface $asset
* @param \Twig_NodeInterface $body
* @param array $inputs
* @param array $filters
* @param string $name
* @param array $attributes
* @param int $lineno
* @param string $tag
*
* @return \Twig_Node
*
* @deprecated since 1.3.0, to be removed in 2.0. Use createBodyNode instead.
*/
protected function createNode(AssetInterface $asset, \Twig_NodeInterface $body, array $inputs, array $filters, $name, array $attributes = array(), $lineno = 0, $tag = null)
{
@trigger_error(sprintf('The %s method is deprecated since 1.3 and will be removed in 2.0. Use createBodyNode instead.', __METHOD__), E_USER_DEPRECATED);
if (!$body instanceof \Twig_Node) {
throw new \InvalidArgumentException('The body must be a Twig_Node. Custom implementations of Twig_NodeInterface are not supported.');
}
return new AsseticNode($asset, $body, $inputs, $filters, $name, $attributes, $lineno, $tag);
}
}

View File

@@ -0,0 +1,108 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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;
use Psr\Log\LoggerInterface;
/**
* Loads asset formulae from Twig templates.
*
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
*/
class TwigFormulaLoader implements FormulaLoaderInterface
{
private $twig;
private $logger;
public function __construct(\Twig_Environment $twig, LoggerInterface $logger = null)
{
$this->twig = $twig;
$this->logger = $logger;
}
public function load(ResourceInterface $resource)
{
try {
$tokens = $this->twig->tokenize(new \Twig_Source($resource->getContent(), (string) $resource));
$nodes = $this->twig->parse($tokens);
} catch (\Exception $e) {
if ($this->logger) {
$this->logger->error(sprintf('The template "%s" contains an error: %s', $resource, $e->getMessage()));
}
return array();
}
return $this->loadNode($nodes);
}
/**
* Loads assets from the supplied node.
*
* @param \Twig_Node $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'),
'vars' => $node->getAttribute('vars'),
),
);
} elseif ($node instanceof AsseticFilterNode) {
$name = $node->getAttribute('name');
$arguments = array();
foreach ($node->getNode('arguments') as $argument) {
$arguments[] = eval('return '.$this->twig->compile($argument).';');
}
$invoker = $this->twig->getExtension('Assetic\Extension\Twig\AsseticExtension')->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);
}
}
if ($node->hasAttribute('embedded_templates')) {
foreach ($node->getAttribute('embedded_templates') as $child) {
$formulae += $this->loadNode($child);
}
}
return $formulae;
}
}

View File

@@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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 method_exists($this->loader, 'getSourceContext')
? $this->loader->getSourceContext($this->name)->getCode()
: $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;
}
}

View File

@@ -0,0 +1,79 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\ValueSupplierInterface;
/**
* Container for values initialized lazily from a ValueSupplierInterface.
*
* @author Christophe Coevoet <stof@notk.org>
*/
class ValueContainer implements \ArrayAccess, \IteratorAggregate, \Countable
{
private $values;
private $valueSupplier;
public function __construct(ValueSupplierInterface $valueSupplier)
{
$this->valueSupplier = $valueSupplier;
}
public function offsetExists($offset)
{
$this->initialize();
return array_key_exists($offset, $this->values);
}
public function offsetGet($offset)
{
$this->initialize();
if (!array_key_exists($offset, $this->values)) {
throw new \OutOfRangeException(sprintf('The variable "%s" does not exist.', $offset));
}
return $this->values[$offset];
}
public function offsetSet($offset, $value)
{
throw new \BadMethodCallException('The ValueContainer is read-only.');
}
public function offsetUnset($offset)
{
throw new \BadMethodCallException('The ValueContainer is read-only.');
}
public function getIterator()
{
$this->initialize();
return new \ArrayIterator($this->values);
}
public function count()
{
$this->initialize();
return count($this->values);
}
private function initialize()
{
if (null === $this->values) {
$this->values = $this->valueSupplier->getValues();
}
}
}

View File

@@ -0,0 +1,424 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\AssetCollectionInterface;
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\Filter\DependencyExtractorInterface;
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 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['vars'])) {
$options['vars'] = array();
}
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(array(), $options);
$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 variables
if (!empty($options['vars'])) {
$toAdd = array();
foreach ($options['vars'] as $var) {
if (false !== strpos($options['output'], '{'.$var.'}')) {
continue;
}
$toAdd[] = '{'.$var.'}';
}
if ($toAdd) {
$options['output'] = str_replace('*', '*.'.implode('.', $toAdd), $options['output']);
}
}
// 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 and return
return $this->applyWorkers($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);
}
public function getLastModified(AssetInterface $asset)
{
$mtime = 0;
foreach ($asset instanceof AssetCollectionInterface ? $asset : array($asset) as $leaf) {
$mtime = max($mtime, $leaf->getLastModified());
if (!$filters = $leaf->getFilters()) {
continue;
}
$prevFilters = array();
foreach ($filters as $filter) {
$prevFilters[] = $filter;
if (!$filter instanceof DependencyExtractorInterface) {
continue;
}
// extract children from leaf after running all preceeding filters
$clone = clone $leaf;
$clone->clearFilters();
foreach (array_slice($prevFilters, 0, -1) as $prevFilter) {
$clone->ensureFilter($prevFilter);
}
$clone->load();
foreach ($filter->getChildren($this, $clone->getContent(), $clone->getSourceDirectory()) as $child) {
$mtime = max($mtime, $this->getLastModified($child));
}
}
}
return $mtime;
}
/**
* 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, $options['vars']);
}
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, $options['vars']);
}
return $this->createFileAsset($input, $root, $path, $options['vars']);
}
protected function createAssetCollection(array $assets = array(), array $options = array())
{
return new AssetCollection($assets, array(), null, isset($options['vars']) ? $options['vars'] : array());
}
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, $vars)
{
return new HttpAsset($sourceUrl, array(), false, $vars);
}
protected function createGlobAsset($glob, $root = null, $vars)
{
return new GlobAsset($glob, array(), $root, $vars);
}
protected function createFileAsset($source, $root = null, $path = null, $vars)
{
return new FileAsset($source, array(), $root, $path, $vars);
}
protected function getFilter($name)
{
if (!$this->fm) {
throw new \LogicException('There is no filter manager.');
}
return $this->fm->get($name);
}
/**
* Filters an asset collection through the factory workers.
*
* Each leaf asset will be processed first, followed by the asset
* collection itself.
*
* @param AssetCollectionInterface $asset An asset collection
*
* @return AssetCollectionInterface
*/
private function applyWorkers(AssetCollectionInterface $asset)
{
foreach ($asset as $leaf) {
foreach ($this->workers as $worker) {
$retval = $worker->process($leaf, $this);
if ($retval instanceof AssetInterface && $leaf !== $retval) {
$asset->replaceLeaf($leaf, $retval);
}
}
}
foreach ($this->workers as $worker) {
$retval = $worker->process($asset, $this);
if ($retval instanceof AssetInterface) {
$asset = $retval;
}
}
return $asset instanceof AssetCollectionInterface ? $asset : $this->createAssetCollection(array($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;
}
}
}
}

View File

@@ -0,0 +1,210 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\AssetInterface;
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();
}
public function getLastModified(AssetInterface $asset)
{
return $this->factory->getLastModified($asset);
}
}

View File

@@ -0,0 +1,160 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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;
use Assetic\Util\FilesystemUtils;
/**
* 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 = FilesystemUtils::createTemporaryFile('php_formula_loader');
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)));
}
}

View File

@@ -0,0 +1,68 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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;
}
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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);
}

View File

@@ -0,0 +1,53 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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;
}
}

View File

@@ -0,0 +1,112 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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);
}
}

View File

@@ -0,0 +1,133 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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];
}
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);
}
}

View File

@@ -0,0 +1,47 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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;
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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
{
}

View File

@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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();
}

View File

@@ -0,0 +1,70 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\AssetCollectionInterface;
use Assetic\Asset\AssetInterface;
use Assetic\Factory\AssetFactory;
/**
* Adds cache busting code
*
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
*/
class CacheBustingWorker implements WorkerInterface
{
private $separator;
public function __construct($separator = '-')
{
$this->separator = $separator;
}
public function process(AssetInterface $asset, AssetFactory $factory)
{
if (!$path = $asset->getTargetPath()) {
// no path to work with
return;
}
if (!$search = pathinfo($path, PATHINFO_EXTENSION)) {
// nothing to replace
return;
}
$replace = $this->separator.$this->getHash($asset, $factory).'.'.$search;
if (preg_match('/'.preg_quote($replace, '/').'$/', $path)) {
// already replaced
return;
}
$asset->setTargetPath(
preg_replace('/\.'.preg_quote($search, '/').'$/', $replace, $path)
);
}
protected function getHash(AssetInterface $asset, AssetFactory $factory)
{
$hash = hash_init('sha1');
hash_update($hash, $factory->getLastModified($asset));
if ($asset instanceof AssetCollectionInterface) {
foreach ($asset as $i => $leaf) {
$sourcePath = $leaf->getSourcePath();
hash_update($hash, $sourcePath ?: $i);
}
}
return substr(hash_final($hash), 0, 7);
}
}

View File

@@ -0,0 +1,61 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Factory\AssetFactory;
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, AssetFactory $factory)
{
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);
}
}
}

View File

@@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Factory\AssetFactory;
/**
* 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
* @param AssetFactory $factory The factory
*
* @return AssetInterface|null May optionally return a replacement asset
*/
public function process(AssetInterface $asset, AssetFactory $factory);
}

View File

@@ -0,0 +1,87 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2013 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\Exception\FilterException;
use Assetic\Util\FilesystemUtils;
/**
* Parses CSS and adds vendor prefixes to rules using values from the Can I Use website
*
* @link https://github.com/ai/autoprefixer
* @author Alex Vasilenko <aa.vasilenko@gmail.com>
*/
class AutoprefixerFilter extends BaseNodeFilter
{
/**
* @var string
*/
private $autoprefixerBin;
/**
* @var array
*/
private $browsers = array();
public function __construct($autoprefixerBin)
{
$this->autoprefixerBin = $autoprefixerBin;
}
/**
* @param array $browsers
*/
public function setBrowsers(array $browsers)
{
$this->browsers = $browsers;
}
/**
* @param string $browser
*/
public function addBrowser($browser)
{
$this->browsers[] = $browser;
}
public function filterLoad(AssetInterface $asset)
{
$input = $asset->getContent();
$pb = $this->createProcessBuilder(array($this->autoprefixerBin));
$pb->setInput($input);
if ($this->browsers) {
$pb->add('-b')->add(implode(',', $this->browsers));
}
$output = FilesystemUtils::createTemporaryFile('autoprefixer');
$pb->add('-o')->add($output);
$proc = $pb->getProcess();
if (0 !== $proc->run()) {
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
}
$asset->setContent(file_get_contents($output));
unlink($output);
}
/**
* Filters an asset just before it's dumped.
*
* @param AssetInterface $asset An asset
*/
public function filterDump(AssetInterface $asset)
{
}
}

View File

@@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Util\CssUtils;
/**
* An abstract filter for dealing with CSS.
*
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
*/
abstract class BaseCssFilter implements FilterInterface
{
/**
* @see CssUtils::filterReferences()
*/
protected function filterReferences($content, $callback, $limit = -1, &$count = 0)
{
return CssUtils::filterReferences($content, $callback, $limit, $count);
}
/**
* @see CssUtils::filterUrls()
*/
protected function filterUrls($content, $callback, $limit = -1, &$count = 0)
{
return CssUtils::filterUrls($content, $callback, $limit, $count);
}
/**
* @see CssUtils::filterImports()
*/
protected function filterImports($content, $callback, $limit = -1, &$count = 0, $includeUrl = true)
{
return CssUtils::filterImports($content, $callback, $limit, $count, $includeUrl);
}
/**
* @see CssUtils::filterIEFilters()
*/
protected function filterIEFilters($content, $callback, $limit = -1, &$count = 0)
{
return CssUtils::filterIEFilters($content, $callback, $limit, $count);
}
}

View File

@@ -0,0 +1,44 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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;
abstract class BaseNodeFilter extends BaseProcessFilter
{
private $nodePaths = array();
public function getNodePaths()
{
return $this->nodePaths;
}
public function setNodePaths(array $nodePaths)
{
$this->nodePaths = $nodePaths;
}
public function addNodePath($nodePath)
{
$this->nodePaths[] = $nodePath;
}
protected function createProcessBuilder(array $arguments = array())
{
$pb = parent::createProcessBuilder($arguments);
if ($this->nodePaths) {
$this->mergeEnv($pb);
$pb->setEnv('NODE_PATH', implode(PATH_SEPARATOR, $this->nodePaths));
}
return $pb;
}
}

View File

@@ -0,0 +1,57 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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 Symfony\Component\Process\ProcessBuilder;
/**
* An external process based filter which provides a way to set a timeout on the process.
*/
abstract class BaseProcessFilter implements FilterInterface
{
private $timeout;
/**
* Set the process timeout.
*
* @param int $timeout The timeout for the process
*/
public function setTimeout($timeout)
{
$this->timeout = $timeout;
}
/**
* Creates a new process builder.
*
* @param array $arguments An optional array of arguments
*
* @return ProcessBuilder A new process builder
*/
protected function createProcessBuilder(array $arguments = array())
{
$pb = new ProcessBuilder($arguments);
if (null !== $this->timeout) {
$pb->setTimeout($this->timeout);
}
return $pb;
}
protected function mergeEnv(ProcessBuilder $pb)
{
foreach (array_filter($_SERVER, 'is_scalar') as $key => $value) {
$pb->setEnv($key, $value);
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Factory\AssetFactory;
/**
* A filter that wraps callables.
*
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
*/
class CallablesFilter implements FilterInterface, DependencyExtractorInterface
{
private $loader;
private $dumper;
private $extractor;
/**
* @param callable|null $loader
* @param callable|null $dumper
* @param callable|null $extractor
*/
public function __construct($loader = null, $dumper = null, $extractor = null)
{
$this->loader = $loader;
$this->dumper = $dumper;
$this->extractor = $extractor;
}
public function filterLoad(AssetInterface $asset)
{
if (null !== $callable = $this->loader) {
$callable($asset);
}
}
public function filterDump(AssetInterface $asset)
{
if (null !== $callable = $this->dumper) {
$callable($asset);
}
}
public function getChildren(AssetFactory $factory, $content, $loadPath = null)
{
if (null !== $callable = $this->extractor) {
return $callable($factory, $content, $loadPath);
}
return array();
}
}

View File

@@ -0,0 +1,343 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Exception\FilterException;
/**
* CleanCss filter.
*
* @link https://github.com/jakubpawlowicz/clean-css
* @author Jakub Pawlowicz <http://JakubPawlowicz.com>
*/
class CleanCssFilter extends BaseNodeFilter
{
private $cleanCssBin;
private $nodeBin;
private $keepLineBreaks;
private $compatibility;
private $debug;
private $rootPath;
private $skipImport = true;
private $timeout;
private $semanticMerging;
private $roundingPrecision;
private $removeSpecialComments;
private $onlyKeepFirstSpecialComment;
private $skipAdvanced;
private $skipAggresiveMerging;
private $skipImportFrom;
private $mediaMerging;
private $skipRebase;
private $skipRestructuring;
private $skipShorthandCompacting;
private $sourceMap;
private $sourceMapInlineSources;
/**
* @param string $cleanCssBin Absolute path to the cleancss executable
* @param string $nodeBin Absolute path to the folder containg node.js executable
*/
public function __construct($cleanCssBin = '/usr/bin/cleancss', $nodeBin = null)
{
$this->cleanCssBin = $cleanCssBin;
$this->nodeBin = $nodeBin;
}
/**
* Keep line breaks
* @param bool $keepLineBreaks True to enable
*/
public function setKeepLineBreaks($keepLineBreaks)
{
$this->keepLineBreaks = $keepLineBreaks;
}
/**
* Remove all special comments
* @param bool $removeSpecialComments True to enable
*/ // i.e. /*! comment */
public function setRemoveSpecialComments($removeSpecialComments)
{
$this->removeSpecialComments = $removeSpecialComments;
}
/**
* Remove all special comments except the first one
* @param bool $onlyKeepFirstSpecialComment True to enable
*/
public function setOnlyKeepFirstSpecialComment($onlyKeepFirstSpecialComment)
{
$this->onlyKeepFirstSpecialComment = $onlyKeepFirstSpecialComment;
}
/**
* Enables unsafe mode by assuming BEM-like semantic stylesheets (warning, this may break your styling!)
* @param bool $semanticMerging True to enable
*/
public function setSemanticMerging($semanticMerging)
{
$this->semanticMerging = $semanticMerging;
}
/**
* A root path to which resolve absolute @import rules
* @param string $rootPath
*/
public function setRootPath($rootPath)
{
$this->rootPath = $rootPath;
}
/**
* Disable @import processing
* @param bool $skipImport True to enable
*/
public function setSkipImport($skipImport)
{
$this->skipImport = $skipImport;
}
/**
* Per connection timeout when fetching remote @imports; defaults to 5 seconds
* @param int $timeout
*/
public function setTimeout($timeout)
{
$this->timeout = $timeout;
}
/**
* Disable URLs rebasing
* @param bool $skipRebase True to enable
*/
public function setSkipRebase($skipRebase)
{
$this->skipRebase = $skipRebase;
}
/**
* Disable restructuring optimizations
* @param bool $skipRestructuring True to enable
*/
public function setSkipRestructuring($skipRestructuring)
{
$this->skipRestructuring = $skipRestructuring;
}
/**
* Disable shorthand compacting
* @param bool $skipShorthandCompacting True to enable
*/
public function setSkipShorthandCompacting($skipShorthandCompacting)
{
$this->skipShorthandCompacting = $skipShorthandCompacting;
}
/**
* Enables building input's source map
* @param bool $sourceMap True to enable
*/
public function setSourceMap($sourceMap)
{
$this->sourceMap = $sourceMap;
}
/**
* Enables inlining sources inside source maps
* @param bool $sourceMapInlineSources True to enable
*/
public function setSourceMapInlineSources($sourceMapInlineSources)
{
$this->sourceMapInlineSources = $sourceMapInlineSources;
}
/**
* Disable advanced optimizations - selector & property merging, reduction, etc.
* @param bool $skipAdvanced True to enable
*/
public function setSkipAdvanced($skipAdvanced)
{
$this->skipAdvanced = $skipAdvanced;
}
/**
* Disable properties merging based on their order
* @param bool $skipAggresiveMerging True to enable
*/
public function setSkipAggresiveMerging($skipAggresiveMerging)
{
$this->skipAggresiveMerging = $skipAggresiveMerging;
}
/**
* Disable @import processing for specified rules
* @param string $skipImportFrom
*/
public function setSkipImportFrom($skipImportFrom)
{
$this->skipImportFrom = $skipImportFrom;
}
/**
* Disable @media merging
* @param bool $mediaMerging True to enable
*/
public function setMediaMerging($mediaMerging)
{
$this->mediaMerging = $mediaMerging;
}
/**
* Rounds to `N` decimal places. Defaults to 2. -1 disables rounding.
* @param int $roundingPrecision
*/
public function setRoundingPrecision($roundingPrecision)
{
$this->roundingPrecision = $roundingPrecision;
}
/**
* Force compatibility mode (see https://github.com/jakubpawlowicz/clean-css/blob/master/README.md#how-to-set-compatibility-mode for advanced examples)
* @param string $compatibility
*/
public function setCompatibility($compatibility)
{
$this->compatibility = $compatibility;
}
/**
* Shows debug information (minification time & compression efficiency)
* @param bool $debug True to enable
*/
public function setDebug($debug)
{
$this->debug = $debug;
}
/**
* @see Assetic\Filter\FilterInterface::filterLoad()
*/
public function filterLoad(AssetInterface $asset)
{
}
/**
* Run the asset through CleanCss
*
* @see Assetic\Filter\FilterInterface::filterDump()
*/
public function filterDump(AssetInterface $asset)
{
$pb = $this->createProcessBuilder($this->nodeBin
? array($this->nodeBin, $this->cleanCssBin)
: array($this->cleanCssBin));
if ($this->keepLineBreaks) {
$pb->add('--keep-line-breaks');
}
if ($this->compatibility) {
$pb->add('--compatibility ' .$this->compatibility);
}
if ($this->debug) {
$pb->add('--debug');
}
if ($this->rootPath) {
$pb->add('--root ' .$this->rootPath);
}
if ($this->skipImport) {
$pb->add('--skip-import');
}
if ($this->timeout) {
$pb->add('--timeout ' .$this->timeout);
}
if ($this->roundingPrecision) {
$pb->add('--rounding-precision ' .$this->roundingPrecision);
}
if ($this->removeSpecialComments) {
$pb->add('--s0');
}
if ($this->onlyKeepFirstSpecialComment) {
$pb->add('--s1');
}
if ($this->semanticMerging) {
$pb->add('--semantic-merging');
}
if ($this->skipAdvanced) {
$pb->add('--skip-advanced');
}
if ($this->skipAggresiveMerging) {
$pb->add('--skip-aggressive-merging');
}
if ($this->skipImportFrom) {
$pb->add('--skip-import-from ' .$this->skipImportFrom);
}
if ($this->mediaMerging) {
$pb->add('--skip-media-merging');
}
if ($this->skipRebase) {
$pb->add('--skip-rebase');
}
if ($this->skipRestructuring) {
$pb->add('--skip-restructuring');
}
if ($this->skipShorthandCompacting) {
$pb->add('--skip-shorthand-compacting');
}
if ($this->sourceMap) {
$pb->add('--source-map');
}
if ($this->sourceMapInlineSources) {
$pb->add('--source-map-inline-sources');
}
// input and output files
$input = tempnam(sys_get_temp_dir(), 'input');
file_put_contents($input, $asset->getContent());
$pb->add($input);
$proc = $pb->getProcess();
$code = $proc->run();
unlink($input);
if (127 === $code) {
throw new \RuntimeException('Path to node executable could not be resolved.');
}
if (0 !== $code) {
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
}
$asset->setContent($proc->getOutput());
}
}

View File

@@ -0,0 +1,83 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Exception\FilterException;
use Assetic\Util\FilesystemUtils;
/**
* Compiles CoffeeScript into Javascript.
*
* @link http://coffeescript.org/
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
*/
class CoffeeScriptFilter extends BaseNodeFilter
{
private $coffeeBin;
private $nodeBin;
// coffee options
private $bare;
private $noHeader;
public function __construct($coffeeBin = '/usr/bin/coffee', $nodeBin = null)
{
$this->coffeeBin = $coffeeBin;
$this->nodeBin = $nodeBin;
}
public function setBare($bare)
{
$this->bare = $bare;
}
public function setNoHeader($noHeader)
{
$this->noHeader = $noHeader;
}
public function filterLoad(AssetInterface $asset)
{
$input = FilesystemUtils::createTemporaryFile('coffee');
file_put_contents($input, $asset->getContent());
$pb = $this->createProcessBuilder($this->nodeBin
? array($this->nodeBin, $this->coffeeBin)
: array($this->coffeeBin));
$pb->add('-cp');
if ($this->bare) {
$pb->add('--bare');
}
if ($this->noHeader) {
$pb->add('--no-header');
}
$pb->add($input);
$proc = $pb->getProcess();
$code = $proc->run();
unlink($input);
if (0 !== $code) {
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
}
$asset->setContent($proc->getOutput());
}
public function filterDump(AssetInterface $asset)
{
}
}

View File

@@ -0,0 +1,391 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Exception\FilterException;
use Assetic\Filter\Sass\BaseSassFilter;
use Assetic\Util\FilesystemUtils;
/**
* Loads Compass files.
*
* @link http://compass-style.org/
* @author Maxime Thirouin <maxime.thirouin@gmail.com>
*/
class CompassFilter extends BaseSassFilter
{
private $compassPath;
private $rubyPath;
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;
private $fontsDir;
private $relativeAssets;
// compass configuration file options
private $plugins = array();
private $httpPath;
private $httpImagesPath;
private $httpFontsPath;
private $httpGeneratedImagesPath;
private $generatedImagesPath;
private $httpJavascriptsPath;
private $homeEnv = true;
public function __construct($compassPath = '/usr/bin/compass', $rubyPath = null)
{
$this->compassPath = $compassPath;
$this->rubyPath = $rubyPath;
$this->cacheLocation = FilesystemUtils::getTemporaryDirectory();
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;
}
public function setFontsDir($fontsDir)
{
$this->fontsDir = $fontsDir;
}
// compass configuration file options setters
public function setPlugins(array $plugins)
{
$this->plugins = $plugins;
}
public function addPlugin($plugin)
{
$this->plugins[] = $plugin;
}
public function setHttpPath($httpPath)
{
$this->httpPath = $httpPath;
}
public function setHttpImagesPath($httpImagesPath)
{
$this->httpImagesPath = $httpImagesPath;
}
public function setHttpFontsPath($httpFontsPath)
{
$this->httpFontsPath = $httpFontsPath;
}
public function setHttpGeneratedImagesPath($httpGeneratedImagesPath)
{
$this->httpGeneratedImagesPath = $httpGeneratedImagesPath;
}
public function setGeneratedImagesPath($generatedImagesPath)
{
$this->generatedImagesPath = $generatedImagesPath;
}
public function setHttpJavascriptsPath($httpJavascriptsPath)
{
$this->httpJavascriptsPath = $httpJavascriptsPath;
}
public function setHomeEnv($homeEnv)
{
$this->homeEnv = $homeEnv;
}
public function setRelativeAssets($relativeAssets)
{
$this->relativeAssets = $relativeAssets;
}
public function filterLoad(AssetInterface $asset)
{
$loadPaths = $this->loadPaths;
if ($dir = $asset->getSourceDirectory()) {
$loadPaths[] = $dir;
}
$tempDir = $this->cacheLocation ? $this->cacheLocation : FilesystemUtils::getTemporaryDirectory();
$compassProcessArgs = array(
$this->compassPath,
'compile',
$tempDir,
);
if (null !== $this->rubyPath) {
$compassProcessArgs = array_merge(explode(' ', $this->rubyPath), $compassProcessArgs);
}
$pb = $this->createProcessBuilder($compassProcessArgs);
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 three 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->relativeAssets) {
$pb->add('--relative-assets');
}
if ($this->javascriptsDir) {
$pb->add('--javascripts-dir')->add($this->javascriptsDir);
}
if ($this->fontsDir) {
$pb->add('--fonts-dir')->add($this->fontsDir);
}
// 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->httpFontsPath) {
$optionsConfig['http_fonts_path'] = $this->httpFontsPath;
}
if ($this->httpGeneratedImagesPath) {
$optionsConfig['http_generated_images_path'] = $this->httpGeneratedImagesPath;
}
if ($this->generatedImagesPath) {
$optionsConfig['generated_images_path'] = $this->generatedImagesPath;
}
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 = $asset->getSourcePath()) {
// 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';
if ($this->homeEnv) {
// it's not really usefull but... https://github.com/chriseppstein/compass/issues/376
$pb->setEnv('HOME', FilesystemUtils::getTemporaryDirectory());
$this->mergeEnv($pb);
}
$proc = $pb->getProcess();
$code = $proc->run();
if (0 !== $code) {
unlink($input);
if (isset($configFile)) {
unlink($configFile);
}
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
}
$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;
}
}

View File

@@ -0,0 +1,65 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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;
/**
* Class CssCacheBustingFilter
*
* @package Assetic\Filter
* @author Maximilian Reichel <info@phramz.com>
*/
class CssCacheBustingFilter extends BaseCssFilter
{
private $version;
private $format = '%s?%s';
public function setVersion($version)
{
$this->version = $version;
}
public function setFormat($versionFormat)
{
$this->format = $versionFormat;
}
public function filterLoad(AssetInterface $asset)
{
}
public function filterDump(AssetInterface $asset)
{
if (!$this->version) {
return;
}
$version = $this->version;
$format = $this->format;
$asset->setContent($this->filterReferences(
$asset->getContent(),
function ($matches) use ($version, $format) {
if (0 === strpos($matches['url'], 'data:')) {
return $matches[0];
}
return str_replace(
$matches['url'],
sprintf($format, $matches['url'], $version),
$matches[0]
);
}
));
}
}

View File

@@ -0,0 +1,143 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Exception\FilterException;
use Assetic\Factory\AssetFactory;
use Assetic\Util\FilesystemUtils;
/**
* CSSEmbed filter
*
* @link https://github.com/nzakas/cssembed
* @author Maxime Thirouin <maxime.thirouin@gmail.com>
*/
class CssEmbedFilter extends BaseProcessFilter implements DependencyExtractorInterface
{
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 = $this->createProcessBuilder(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) {
if ($dir = $asset->getSourceDirectory()) {
$pb->add('--root')->add($dir);
}
} 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 = FilesystemUtils::createTemporaryFile('cssembed'));
file_put_contents($input, $asset->getContent());
$proc = $pb->getProcess();
$code = $proc->run();
unlink($input);
if (0 !== $code) {
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
}
$asset->setContent($proc->getOutput());
}
public function getChildren(AssetFactory $factory, $content, $loadPath = null)
{
// todo
return array();
}
}

View File

@@ -0,0 +1,108 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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;
use Assetic\Factory\AssetFactory;
/**
* Inlines imported stylesheets.
*
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
*/
class CssImportFilter extends BaseCssFilter implements DependencyExtractorInterface
{
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);
$importRoot = '//'.$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];
}
$importSource = $importRoot.'/'.$importPath;
if (false !== strpos($importSource, '://') || 0 === strpos($importSource, '//')) {
$import = new HttpAsset($importSource, array($importFilter), true);
} elseif ('css' != pathinfo($importPath, PATHINFO_EXTENSION) || !file_exists($importSource)) {
// ignore non-css and non-existant 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)
{
}
public function getChildren(AssetFactory $factory, $content, $loadPath = null)
{
// todo
return array();
}
}

View File

@@ -0,0 +1,72 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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.
*
* @link http://code.google.com/p/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']) {
if ($dir = $asset->getSourceDirectory()) {
$filters['ImportImports'] = array('BasePath' => $dir);
} else {
unset($filters['ImportImports']);
}
}
$asset->setContent(\CssMin::minify($asset->getContent(), $filters, $plugins));
}
}

View File

@@ -0,0 +1,102 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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 (isset($matches['url'][0]) && '/' == $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);
}
$parts = array();
foreach (explode('/', $host.$path.$url) as $part) {
if ('..' === $part && count($parts) && '..' !== end($parts)) {
array_pop($parts);
} else {
$parts[] = $part;
}
}
return str_replace($matches['url'], implode('/', $parts), $matches[0]);
});
$asset->setContent($content);
}
}

View File

@@ -0,0 +1,73 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Exception\FilterException;
use Assetic\Util\FilesystemUtils;
/**
* Compiles Dart into Javascript.
*
* @link http://dartlang.org/
*/
class DartFilter extends BaseProcessFilter
{
private $dartBin;
public function __construct($dartBin = '/usr/bin/dart2js')
{
$this->dartBin = $dartBin;
}
public function filterLoad(AssetInterface $asset)
{
$input = FilesystemUtils::createTemporaryFile('dart');
$output = FilesystemUtils::createTemporaryFile('dart');
file_put_contents($input, $asset->getContent());
$pb = $this->createProcessBuilder()
->add($this->dartBin)
->add('-o'.$output)
->add($input)
;
$proc = $pb->getProcess();
$code = $proc->run();
unlink($input);
if (0 !== $code) {
$this->cleanup($output);
throw FilterException::fromProcess($proc);
}
if (!file_exists($output)) {
throw new \RuntimeException('Error creating output file.');
}
$asset->setContent(file_get_contents($output));
$this->cleanup($output);
}
public function filterDump(AssetInterface $asset)
{
}
private function cleanup($file)
{
foreach (glob($file.'*') as $related) {
unlink($related);
}
}
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Factory\AssetFactory;
/**
* A filter that knows how to extract dependencies.
*
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
*/
interface DependencyExtractorInterface extends FilterInterface
{
/**
* Returns child assets.
*
* @param AssetFactory $factory The asset factory
* @param string $content The asset content
* @param string $loadPath An optional load path
*
* @return AssetInterface[] Child assets
*/
public function getChildren(AssetFactory $factory, $content, $loadPath = null);
}

View File

@@ -0,0 +1,87 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Exception\FilterException;
use Assetic\Util\FilesystemUtils;
/**
* Precompiles Handlebars templates for use in the Ember.js framework. This filter
* requires that the npm package ember-precompile be installed. You can find this
* package at https://github.com/gabrielgrant/node-ember-precompile.
*
* @link http://www.emberjs.com/
* @author Jarrod Nettles <jarrod.nettles@icloud.com>
*/
class EmberPrecompileFilter extends BaseNodeFilter
{
private $emberBin;
private $nodeBin;
public function __construct($handlebarsBin = '/usr/bin/ember-precompile', $nodeBin = null)
{
$this->emberBin = $handlebarsBin;
$this->nodeBin = $nodeBin;
}
public function filterLoad(AssetInterface $asset)
{
$pb = $this->createProcessBuilder($this->nodeBin
? array($this->nodeBin, $this->emberBin)
: array($this->emberBin));
if ($sourcePath = $asset->getSourcePath()) {
$templateName = basename($sourcePath);
} else {
throw new \LogicException('The embed-precompile filter requires that assets have a source path set');
}
$inputDirPath = FilesystemUtils::createThrowAwayDirectory('ember_in');
$inputPath = $inputDirPath.DIRECTORY_SEPARATOR.$templateName;
$outputPath = FilesystemUtils::createTemporaryFile('ember_out');
file_put_contents($inputPath, $asset->getContent());
$pb->add($inputPath)->add('-f')->add($outputPath);
$process = $pb->getProcess();
$returnCode = $process->run();
unlink($inputPath);
rmdir($inputDirPath);
if (127 === $returnCode) {
throw new \RuntimeException('Path to node executable could not be resolved.');
}
if (0 !== $returnCode) {
if (file_exists($outputPath)) {
unlink($outputPath);
}
throw FilterException::fromProcess($process)->setInput($asset->getContent());
}
if (!file_exists($outputPath)) {
throw new \RuntimeException('Error creating output file.');
}
$compiledJs = file_get_contents($outputPath);
unlink($outputPath);
$asset->setContent($compiledJs);
}
public function filterDump(AssetInterface $asset)
{
}
}

View File

@@ -0,0 +1,82 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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);
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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);
}

View File

@@ -0,0 +1,101 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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';
// languages
const LANGUAGE_ECMASCRIPT3 = 'ECMASCRIPT3';
const LANGUAGE_ECMASCRIPT5 = 'ECMASCRIPT5';
const LANGUAGE_ECMASCRIPT5_STRICT = 'ECMASCRIPT5_STRICT';
protected $timeout;
protected $compilationLevel;
protected $jsExterns;
protected $externsUrl;
protected $excludeDefaultExterns;
protected $formatting;
protected $useClosureLibrary;
protected $warningLevel;
protected $language;
public function setTimeout($timeout)
{
$this->timeout = $timeout;
}
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 setLanguage($language)
{
$this->language = $language;
}
public function filterLoad(AssetInterface $asset)
{
}
}

View File

@@ -0,0 +1,130 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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.
*
* @link https://developers.google.com/closure/compiler/
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
*/
class CompilerApiFilter extends BaseCompilerFilter
{
private $proxy;
private $proxyFullUri;
public function setProxy($proxy)
{
$this->proxy = $proxy;
}
public function setProxyFullUri($proxyFullUri)
{
$this->proxyFullUri = $proxyFullUri;
}
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;
}
if (null !== $this->language) {
$query['language'] = $this->language;
}
if (preg_match('/1|yes|on|true/i', ini_get('allow_url_fopen'))) {
$contextOptions = array('http' => array(
'method' => 'POST',
'header' => 'Content-Type: application/x-www-form-urlencoded',
'content' => http_build_query($query),
));
if (null !== $this->timeout) {
$contextOptions['http']['timeout'] = $this->timeout;
}
if ($this->proxy) {
$contextOptions['http']['proxy'] = $this->proxy;
$contextOptions['http']['request_fulluri'] = (Boolean) $this->proxyFullUri;
}
$context = stream_context_create($contextOptions);
$response = file_get_contents('http://closure-compiler.appspot.com/compile', false, $context);
$data = json_decode($response);
} elseif (defined('CURLOPT_POST') && !in_array('curl_init', explode(',', ini_get('disable_functions')))) {
$ch = curl_init('http://closure-compiler.appspot.com/compile');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/x-www-form-urlencoded'));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $query);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
if (null !== $this->timeout) {
curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
}
if ($this->proxy) {
curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, true);
curl_setopt($ch, CURLOPT_PROXY, $this->proxy);
}
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response);
} else {
throw new \RuntimeException("There is no known way to contact closure compiler available");
}
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);
}
}

View File

@@ -0,0 +1,112 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Exception\FilterException;
use Assetic\Util\FilesystemUtils;
use Symfony\Component\Process\ProcessBuilder;
/**
* Filter for the Google Closure Compiler JAR.
*
* @link https://developers.google.com/closure/compiler/
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
*/
class CompilerJarFilter extends BaseCompilerFilter
{
private $jarPath;
private $javaPath;
private $flagFile;
public function __construct($jarPath, $javaPath = '/usr/bin/java')
{
$this->jarPath = $jarPath;
$this->javaPath = $javaPath;
}
public function setFlagFile($flagFile)
{
$this->flagFile = $flagFile;
}
public function filterDump(AssetInterface $asset)
{
$is64bit = PHP_INT_SIZE === 8;
$cleanup = array();
$pb = new ProcessBuilder(array_merge(
array($this->javaPath),
$is64bit
? array('-server', '-XX:+TieredCompilation')
: array('-client', '-d32'),
array('-jar', $this->jarPath)
));
if (null !== $this->timeout) {
$pb->setTimeout($this->timeout);
}
if (null !== $this->compilationLevel) {
$pb->add('--compilation_level')->add($this->compilationLevel);
}
if (null !== $this->jsExterns) {
$cleanup[] = $externs = FilesystemUtils::createTemporaryFile('google_closure');
file_put_contents($externs, $this->jsExterns);
$pb->add('--externs')->add($externs);
}
if (null !== $this->externsUrl) {
$cleanup[] = $externs = FilesystemUtils::createTemporaryFile('google_closure');
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);
}
if (null !== $this->language) {
$pb->add('--language_in')->add($this->language);
}
if (null !== $this->flagFile) {
$pb->add('--flagfile')->add($this->flagFile);
}
$pb->add('--js')->add($cleanup[] = $input = FilesystemUtils::createTemporaryFile('google_closure'));
file_put_contents($input, $asset->getContent());
$proc = $pb->getProcess();
$code = $proc->run();
array_map('unlink', $cleanup);
if (0 !== $code) {
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
}
$asset->setContent($proc->getOutput());
}
}

View File

@@ -0,0 +1,142 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Exception\FilterException;
use Assetic\Util\FilesystemUtils;
/**
* Filter for the Google Closure Stylesheets Compiler JAR.
*
* @link http://code.google.com/p/closure-stylesheets/
* @author Matthias Krauser <matthias@krauser.eu>
*/
class GssFilter extends BaseProcessFilter
{
private $jarPath;
private $javaPath;
private $allowUnrecognizedFunctions;
private $allowedNonStandardFunctions;
private $copyrightNotice;
private $define;
private $gssFunctionMapProvider;
private $inputOrientation;
private $outputOrientation;
private $prettyPrint;
public function __construct($jarPath, $javaPath = '/usr/bin/java')
{
$this->jarPath = $jarPath;
$this->javaPath = $javaPath;
}
public function setAllowUnrecognizedFunctions($allowUnrecognizedFunctions)
{
$this->allowUnrecognizedFunctions = $allowUnrecognizedFunctions;
}
public function setAllowedNonStandardFunctions($allowNonStandardFunctions)
{
$this->allowedNonStandardFunctions = $allowNonStandardFunctions;
}
public function setCopyrightNotice($copyrightNotice)
{
$this->copyrightNotice = $copyrightNotice;
}
public function setDefine($define)
{
$this->define = $define;
}
public function setGssFunctionMapProvider($gssFunctionMapProvider)
{
$this->gssFunctionMapProvider = $gssFunctionMapProvider;
}
public function setInputOrientation($inputOrientation)
{
$this->inputOrientation = $inputOrientation;
}
public function setOutputOrientation($outputOrientation)
{
$this->outputOrientation = $outputOrientation;
}
public function setPrettyPrint($prettyPrint)
{
$this->prettyPrint = $prettyPrint;
}
public function filterLoad(AssetInterface $asset)
{
$cleanup = array();
$pb = $this->createProcessBuilder(array(
$this->javaPath,
'-jar',
$this->jarPath,
));
if (null !== $this->allowUnrecognizedFunctions) {
$pb->add('--allow-unrecognized-functions');
}
if (null !== $this->allowedNonStandardFunctions) {
$pb->add('--allowed_non_standard_functions')->add($this->allowedNonStandardFunctions);
}
if (null !== $this->copyrightNotice) {
$pb->add('--copyright-notice')->add($this->copyrightNotice);
}
if (null !== $this->define) {
$pb->add('--define')->add($this->define);
}
if (null !== $this->gssFunctionMapProvider) {
$pb->add('--gss-function-map-provider')->add($this->gssFunctionMapProvider);
}
if (null !== $this->inputOrientation) {
$pb->add('--input-orientation')->add($this->inputOrientation);
}
if (null !== $this->outputOrientation) {
$pb->add('--output-orientation')->add($this->outputOrientation);
}
if (null !== $this->prettyPrint) {
$pb->add('--pretty-print');
}
$pb->add($cleanup[] = $input = FilesystemUtils::createTemporaryFile('gss'));
file_put_contents($input, $asset->getContent());
$proc = $pb->getProcess();
$code = $proc->run();
array_map('unlink', $cleanup);
if (0 !== $code) {
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
}
$asset->setContent($proc->getOutput());
}
public function filterDump(AssetInterface $asset)
{
}
}

View File

@@ -0,0 +1,106 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Exception\FilterException;
use Assetic\Util\FilesystemUtils;
/**
* Compiles Handlebars templates into Javascript.
*
* @link http://handlebarsjs.com/
* @author Keyvan Akbary <keyvan@funddy.com>
*/
class HandlebarsFilter extends BaseNodeFilter
{
private $handlebarsBin;
private $nodeBin;
private $minimize = false;
private $simple = false;
public function __construct($handlebarsBin = '/usr/bin/handlebars', $nodeBin = null)
{
$this->handlebarsBin = $handlebarsBin;
$this->nodeBin = $nodeBin;
}
public function setMinimize($minimize)
{
$this->minimize = $minimize;
}
public function setSimple($simple)
{
$this->simple = $simple;
}
public function filterLoad(AssetInterface $asset)
{
$pb = $this->createProcessBuilder($this->nodeBin
? array($this->nodeBin, $this->handlebarsBin)
: array($this->handlebarsBin));
if ($sourcePath = $asset->getSourcePath()) {
$templateName = basename($sourcePath);
} else {
throw new \LogicException('The handlebars filter requires that assets have a source path set');
}
$inputDirPath = FilesystemUtils::createThrowAwayDirectory('handlebars_in');
$inputPath = $inputDirPath.DIRECTORY_SEPARATOR.$templateName;
$outputPath = FilesystemUtils::createTemporaryFile('handlebars_out');
file_put_contents($inputPath, $asset->getContent());
$pb->add($inputPath)->add('-f')->add($outputPath);
if ($this->minimize) {
$pb->add('--min');
}
if ($this->simple) {
$pb->add('--simple');
}
$process = $pb->getProcess();
$returnCode = $process->run();
unlink($inputPath);
rmdir($inputDirPath);
if (127 === $returnCode) {
throw new \RuntimeException('Path to node executable could not be resolved.');
}
if (0 !== $returnCode) {
if (file_exists($outputPath)) {
unlink($outputPath);
}
throw FilterException::fromProcess($process)->setInput($asset->getContent());
}
if (!file_exists($outputPath)) {
throw new \RuntimeException('Error creating output file.');
}
$compiledJs = file_get_contents($outputPath);
unlink($outputPath);
$asset->setContent($compiledJs);
}
public function filterDump(AssetInterface $asset)
{
}
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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();
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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 JsMin.
*
* All credit for the filter itself is mentioned in the file itself.
*
* @link https://raw.github.com/mrclay/minify/master/min/lib/JSMin.php
* @author Brunoais <brunoaiss@gmail.com>
*/
class JSMinFilter implements FilterInterface
{
public function filterLoad(AssetInterface $asset)
{
}
public function filterDump(AssetInterface $asset)
{
$asset->setContent(\JSMin::minify($asset->getContent()));
}
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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 JSMinPlus.
*
* All credit for the filter itself is mentioned in the file itself.
*
* @link https://raw.github.com/mrclay/minify/master/min/lib/JSMinPlus.php
* @author Brunoais <brunoaiss@gmail.com>
*/
class JSMinPlusFilter implements FilterInterface
{
public function filterLoad(AssetInterface $asset)
{
}
public function filterDump(AssetInterface $asset)
{
$asset->setContent(\JSMinPlus::minify($asset->getContent()));
}
}

View File

@@ -0,0 +1,77 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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;
/**
* JSqueeze filter.
*
* @link https://github.com/nicolas-grekas/JSqueeze
* @author Nicolas Grekas <p@tchwork.com>
*/
class JSqueezeFilter implements FilterInterface
{
private $singleLine = true;
private $keepImportantComments = true;
private $className;
private $specialVarRx = false;
private $defaultRx;
public function __construct()
{
// JSqueeze is namespaced since 2.x, this works with both 1.x and 2.x
if (class_exists('\\Patchwork\\JSqueeze')) {
$this->className = '\\Patchwork\\JSqueeze';
$this->defaultRx = \Patchwork\JSqueeze::SPECIAL_VAR_PACKER;
} else {
$this->className = '\\JSqueeze';
$this->defaultRx = \JSqueeze::SPECIAL_VAR_RX;
}
}
public function setSingleLine($bool)
{
$this->singleLine = (bool) $bool;
}
// call setSpecialVarRx(true) to enable global var/method/property
// renaming with the default regex (for 1.x or 2.x)
public function setSpecialVarRx($specialVarRx)
{
if (true === $specialVarRx) {
$this->specialVarRx = $this->defaultRx;
} else {
$this->specialVarRx = $specialVarRx;
}
}
public function keepImportantComments($bool)
{
$this->keepImportantComments = (bool) $bool;
}
public function filterLoad(AssetInterface $asset)
{
}
public function filterDump(AssetInterface $asset)
{
$parser = new $this->className();
$asset->setContent($parser->squeeze(
$asset->getContent(),
$this->singleLine,
$this->keepImportantComments,
$this->specialVarRx
));
}
}

View File

@@ -0,0 +1,81 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Exception\FilterException;
use Assetic\Util\FilesystemUtils;
/**
* Runs assets through Jpegoptim.
*
* @link http://www.kokkonen.net/tjko/projects.html
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
*/
class JpegoptimFilter extends BaseProcessFilter
{
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 = $this->createProcessBuilder(array($this->jpegoptimBin));
if ($this->stripAll) {
$pb->add('--strip-all');
}
if ($this->max) {
$pb->add('--max='.$this->max);
}
$pb->add($input = FilesystemUtils::createTemporaryFile('jpegoptim'));
file_put_contents($input, $asset->getContent());
$proc = $pb->getProcess();
$proc->run();
if (false !== strpos($proc->getOutput(), 'ERROR')) {
unlink($input);
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
}
$asset->setContent(file_get_contents($input));
unlink($input);
}
}

View File

@@ -0,0 +1,103 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Exception\FilterException;
use Assetic\Util\FilesystemUtils;
/**
* Runs assets through jpegtran.
*
* @link http://jpegclub.org/jpegtran/
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
*/
class JpegtranFilter extends BaseProcessFilter
{
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 = $this->createProcessBuilder(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 = FilesystemUtils::createTemporaryFile('jpegtran'));
file_put_contents($input, $asset->getContent());
$proc = $pb->getProcess();
$code = $proc->run();
unlink($input);
if (0 !== $code) {
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
}
$asset->setContent($proc->getOutput());
}
}

View File

@@ -0,0 +1,206 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Exception\FilterException;
use Assetic\Factory\AssetFactory;
use Assetic\Util\FilesystemUtils;
use Assetic\Util\LessUtils;
/**
* Loads LESS files.
*
* @link http://lesscss.org/
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
*/
class LessFilter extends BaseNodeFilter implements DependencyExtractorInterface
{
private $nodeBin;
/**
* @var array
*/
private $treeOptions;
/**
* @var array
*/
private $parserOptions;
/**
* Load Paths
*
* A list of paths which less will search for includes.
*
* @var array
*/
protected $loadPaths = array();
/**
* 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->setNodePaths($nodePaths);
$this->treeOptions = array();
$this->parserOptions = array();
}
/**
* @param bool $compress
*/
public function setCompress($compress)
{
$this->addTreeOption('compress', $compress);
}
public function setLoadPaths(array $loadPaths)
{
$this->loadPaths = $loadPaths;
}
/**
* Adds a path where less will search for includes
*
* @param string $path Load path (absolute)
*/
public function addLoadPath($path)
{
$this->loadPaths[] = $path;
}
/**
* @param string $code
* @param string $value
*/
public function addTreeOption($code, $value)
{
$this->treeOptions[$code] = $value;
}
/**
* @param string $code
* @param string $value
*/
public function addParserOption($code, $value)
{
$this->parserOptions[$code] = $value;
}
public function filterLoad(AssetInterface $asset)
{
static $format = <<<'EOF'
var less = require('less');
var sys = require(process.binding('natives').util ? 'util' : 'sys');
less.render(%s, %s, function(error, css) {
if (error) {
less.writeError(error);
process.exit(2);
}
try {
if (typeof css == 'string') {
sys.print(css);
} else {
sys.print(css.css);
}
} catch (e) {
less.writeError(error);
process.exit(3);
}
});
EOF;
// parser options
$parserOptions = $this->parserOptions;
if ($dir = $asset->getSourceDirectory()) {
$parserOptions['paths'] = array($dir);
$parserOptions['filename'] = basename($asset->getSourcePath());
}
foreach ($this->loadPaths as $loadPath) {
$parserOptions['paths'][] = $loadPath;
}
$pb = $this->createProcessBuilder();
$pb->add($this->nodeBin)->add($input = FilesystemUtils::createTemporaryFile('less'));
file_put_contents($input, sprintf($format,
json_encode($asset->getContent()),
json_encode(array_merge($parserOptions, $this->treeOptions))
));
$proc = $pb->getProcess();
$code = $proc->run();
unlink($input);
if (0 !== $code) {
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
}
$asset->setContent($proc->getOutput());
}
public function filterDump(AssetInterface $asset)
{
}
/**
* @todo support for import-once
* @todo support for import (less) "lib.css"
*/
public function getChildren(AssetFactory $factory, $content, $loadPath = null)
{
$loadPaths = $this->loadPaths;
if (null !== $loadPath) {
$loadPaths[] = $loadPath;
}
if (empty($loadPaths)) {
return array();
}
$children = array();
foreach (LessUtils::extractImports($content) as $reference) {
if ('.css' === substr($reference, -4)) {
// skip normal css imports
// todo: skip imports with media queries
continue;
}
if ('.less' !== substr($reference, -5)) {
$reference .= '.less';
}
foreach ($loadPaths as $loadPath) {
if (file_exists($file = $loadPath.'/'.$reference)) {
$coll = $factory->createAsset($file, array(), array('root' => $loadPath));
foreach ($coll as $leaf) {
$leaf->ensureFilter($this);
$children[] = $leaf;
goto next_reference;
}
}
}
next_reference:
}
return $children;
}
}

View File

@@ -0,0 +1,167 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Factory\AssetFactory;
use Assetic\Util\LessUtils;
/**
* Loads LESS files using the PHP implementation of less, lessphp.
*
* Less files are mostly compatible, but there are slight differences.
*
* @link http://leafo.net/lessphp/
*
* @author David Buchmann <david@liip.ch>
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
*/
class LessphpFilter implements DependencyExtractorInterface
{
private $presets = array();
private $formatter;
private $preserveComments;
private $customFunctions = array();
private $options = array();
/**
* Lessphp Load Paths
*
* @var array
*/
protected $loadPaths = array();
/**
* Adds a load path to the paths used by lessphp
*
* @param string $path Load Path
*/
public function addLoadPath($path)
{
$this->loadPaths[] = $path;
}
/**
* Sets load paths used by lessphp
*
* @param array $loadPaths Load paths
*/
public function setLoadPaths(array $loadPaths)
{
$this->loadPaths = $loadPaths;
}
public function setPresets(array $presets)
{
$this->presets = $presets;
}
public function setOptions(array $options)
{
$this->options = $options;
}
/**
* @param string $formatter One of "lessjs", "compressed", or "classic".
*/
public function setFormatter($formatter)
{
$this->formatter = $formatter;
}
/**
* @param boolean $preserveComments
*/
public function setPreserveComments($preserveComments)
{
$this->preserveComments = $preserveComments;
}
public function filterLoad(AssetInterface $asset)
{
$lc = new \lessc();
if ($dir = $asset->getSourceDirectory()) {
$lc->importDir = $dir;
}
foreach ($this->loadPaths as $loadPath) {
$lc->addImportDir($loadPath);
}
foreach ($this->customFunctions as $name => $callable) {
$lc->registerFunction($name, $callable);
}
if ($this->formatter) {
$lc->setFormatter($this->formatter);
}
if (null !== $this->preserveComments) {
$lc->setPreserveComments($this->preserveComments);
}
if (method_exists($lc, 'setOptions') && count($this->options) > 0 ) {
$lc->setOptions($this->options);
}
$asset->setContent($lc->parse($asset->getContent(), $this->presets));
}
public function registerFunction($name, $callable)
{
$this->customFunctions[$name] = $callable;
}
public function filterDump(AssetInterface $asset)
{
}
public function getChildren(AssetFactory $factory, $content, $loadPath = null)
{
$loadPaths = $this->loadPaths;
if (null !== $loadPath) {
$loadPaths[] = $loadPath;
}
if (empty($loadPaths)) {
return array();
}
$children = array();
foreach (LessUtils::extractImports($content) as $reference) {
if ('.css' === substr($reference, -4)) {
// skip normal css imports
// todo: skip imports with media queries
continue;
}
if ('.less' !== substr($reference, -5)) {
$reference .= '.less';
}
foreach ($loadPaths as $loadPath) {
if (file_exists($file = $loadPath.'/'.$reference)) {
$coll = $factory->createAsset($file, array(), array('root' => $loadPath));
foreach ($coll as $leaf) {
$leaf->ensureFilter($this);
$children[] = $leaf;
goto next_reference;
}
}
}
next_reference:
}
return $children;
}
}

View File

@@ -0,0 +1,35 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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 Minify_CSS_Compressor.
*
* All credit for the filter itself is mentioned in the file itself.
*
* @link https://raw.githubusercontent.com/mrclay/minify/master/min/lib/Minify/CSS/Compressor.php
* @author Stephen Clay <steve@mrclay.org>
* @author http://code.google.com/u/1stvamp/ (Issue 64 patch)
*/
class MinifyCssCompressorFilter implements FilterInterface
{
public function filterLoad(AssetInterface $asset)
{
}
public function filterDump(AssetInterface $asset)
{
$asset->setContent(\Minify_CSS_Compressor::process($asset->getContent()));
}
}

View File

@@ -0,0 +1,75 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Exception\FilterException;
use Assetic\Util\FilesystemUtils;
/**
* Runs assets through OptiPNG.
*
* @link http://optipng.sourceforge.net/
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
*/
class OptiPngFilter extends BaseProcessFilter
{
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 = $this->createProcessBuilder(array($this->optipngBin));
if (null !== $this->level) {
$pb->add('-o')->add($this->level);
}
$pb->add('-out')->add($output = FilesystemUtils::createTemporaryFile('optipng_out'));
unlink($output);
$pb->add($input = FilesystemUtils::createTemporaryFile('optinpg_in'));
file_put_contents($input, $asset->getContent());
$proc = $pb->getProcess();
$code = $proc->run();
if (0 !== $code) {
unlink($input);
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
}
$asset->setContent(file_get_contents($output));
unlink($input);
unlink($output);
}
}

View File

@@ -0,0 +1,65 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\FilesystemUtils;
/**
* 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 = FilesystemUtils::getTemporaryDirectory().'/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)
{
}
}

View File

@@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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, a JavaScript Compressor/Obfuscator.
*
* PHP Version of the Dean Edwards's Packer, ported by Nicolas Martin.
*
* @link http://joliclic.free.fr/php/javascript-packer/en/
* @author Maximilian Walter <github@max-walter.net>
*/
class PackerFilter implements FilterInterface
{
protected $encoding = 'None';
protected $fastDecode = true;
protected $specialChars = false;
public function setEncoding($encoding)
{
$this->encoding = $encoding;
}
public function setFastDecode($fastDecode)
{
$this->fastDecode = (bool) $fastDecode;
}
public function setSpecialChars($specialChars)
{
$this->specialChars = (bool) $specialChars;
}
public function filterLoad(AssetInterface $asset)
{
}
public function filterDump(AssetInterface $asset)
{
$packer = new \JavaScriptPacker($asset->getContent(), $this->encoding, $this->fastDecode, $this->specialChars);
$asset->setContent($packer->pack());
}
}

View File

@@ -0,0 +1,52 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Factory\AssetFactory;
use CssEmbed\CssEmbed;
/**
* A filter that embed url directly into css
*
* @author Pierre Tachoire <pierre.tachoire@gmail.com>
* @link https://github.com/krichprollsch/phpCssEmbed
*/
class PhpCssEmbedFilter implements DependencyExtractorInterface
{
private $presets = array();
public function setPresets(array $presets)
{
$this->presets = $presets;
}
public function filterLoad(AssetInterface $asset)
{
$pce = new CssEmbed();
if ($dir = $asset->getSourceDirectory()) {
$pce->setRootDir($dir);
}
$asset->setContent($pce->embedString($asset->getContent()));
}
public function filterDump(AssetInterface $asset)
{
}
public function getChildren(AssetFactory $factory, $content, $loadPath = null)
{
// todo
return array();
}
}

View File

@@ -0,0 +1,128 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Exception\FilterException;
use Assetic\Util\FilesystemUtils;
/**
* Runs assets through pngout.
*
* @link http://advsys.net/ken/utils.htm#pngout
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
*/
class PngoutFilter extends BaseProcessFilter
{
// -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 = $this->createProcessBuilder(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 = FilesystemUtils::createTemporaryFile('pngout_in'));
file_put_contents($input, $asset->getContent());
$output = FilesystemUtils::createTemporaryFile('pngout_out');
unlink($output);
$pb->add($output .= '.png');
$proc = $pb->getProcess();
$code = $proc->run();
if (0 !== $code) {
unlink($input);
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
}
$asset->setContent(file_get_contents($output));
unlink($input);
unlink($output);
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace Assetic\Filter;
use Assetic\Asset\AssetInterface;
use Assetic\Exception\FilterException;
use Assetic\Util\FilesystemUtils;
/**
* Compiles JSX (for use with React) into JavaScript.
*
* @link http://facebook.github.io/react/docs/jsx-in-depth.html
* @author Douglas Greenshields <dgreenshields@gmail.com>
*/
class ReactJsxFilter extends BaseNodeFilter
{
private $jsxBin;
private $nodeBin;
public function __construct($jsxBin = '/usr/bin/jsx', $nodeBin = null)
{
$this->jsxBin = $jsxBin;
$this->nodeBin = $nodeBin;
}
public function filterLoad(AssetInterface $asset)
{
$builder = $this->createProcessBuilder($this->nodeBin
? array($this->nodeBin, $this->jsxBin)
: array($this->jsxBin));
$inputDir = FilesystemUtils::createThrowAwayDirectory('jsx_in');
$inputFile = $inputDir.DIRECTORY_SEPARATOR.'asset.js';
$outputDir = FilesystemUtils::createThrowAwayDirectory('jsx_out');
$outputFile = $outputDir.DIRECTORY_SEPARATOR.'asset.js';
// create the asset file
file_put_contents($inputFile, $asset->getContent());
$builder
->add($inputDir)
->add($outputDir)
->add('--no-cache-dir')
;
$proc = $builder->getProcess();
$code = $proc->run();
// remove the input directory and asset file
unlink($inputFile);
rmdir($inputDir);
if (0 !== $code) {
if (file_exists($outputFile)) {
unlink($outputFile);
}
if (file_exists($outputDir)) {
rmdir($outputDir);
}
throw FilterException::fromProcess($proc);
}
$asset->setContent(file_get_contents($outputFile));
// remove the output directory and processed asset file
unlink($outputFile);
rmdir($outputDir);
}
public function filterDump(AssetInterface $asset)
{
}
}

View File

@@ -0,0 +1,84 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Exception\FilterException;
use Assetic\Factory\AssetFactory;
use Assetic\Util\FilesystemUtils;
/**
* Loads Roole files.
*
* @link http://roole.org
* @author Marcin Chwedziak <tiraeth@gmail.com>
*/
class RooleFilter extends BaseNodeFilter implements DependencyExtractorInterface
{
private $rooleBin;
private $nodeBin;
/**
* Constructor
*
* @param string $rooleBin The path to the roole binary
* @param string $nodeBin The path to the node binary
*/
public function __construct($rooleBin = '/usr/bin/roole', $nodeBin = null)
{
$this->rooleBin = $rooleBin;
$this->nodeBin = $nodeBin;
}
public function filterLoad(AssetInterface $asset)
{
$input = FilesystemUtils::createTemporaryFile('roole');
$output = $input.'.css';
file_put_contents($input, $asset->getContent());
$pb = $this->createProcessBuilder($this->nodeBin
? array($this->nodeBin, $this->rooleBin)
: array($this->rooleBin));
$pb->add($input);
$proc = $pb->getProcess();
$code = $proc->run();
unlink($input);
if (0 !== $code) {
if (file_exists($output)) {
unlink($output);
}
throw FilterException::fromProcess($proc);
}
if (!file_exists($output)) {
throw new \RuntimeException('Error creating output file.');
}
$asset->setContent(file_get_contents($output));
unlink($output);
}
public function filterDump(AssetInterface $asset)
{
}
public function getChildren(AssetFactory $factory, $content, $loadPath = null)
{
// todo
return array();
}
}

View File

@@ -0,0 +1,95 @@
<?php
namespace Assetic\Filter\Sass;
use Assetic\Asset\AssetInterface;
use Assetic\Factory\AssetFactory;
use Assetic\Filter\BaseProcessFilter;
use Assetic\Filter\DependencyExtractorInterface;
use Assetic\Util\SassUtils;
abstract class BaseSassFilter extends BaseProcessFilter implements DependencyExtractorInterface
{
protected $loadPaths = array();
public function setLoadPaths(array $loadPaths)
{
$this->loadPaths = $loadPaths;
}
public function addLoadPath($loadPath)
{
$this->loadPaths[] = $loadPath;
}
public function getChildren(AssetFactory $factory, $content, $loadPath = null)
{
$loadPaths = $this->loadPaths;
if ($loadPath) {
array_unshift($loadPaths, $loadPath);
}
if (!$loadPaths) {
return array();
}
$children = array();
foreach (SassUtils::extractImports($content) as $reference) {
if ('.css' === substr($reference, -4)) {
// skip normal css imports
// todo: skip imports with media queries
continue;
}
// the reference may or may not have an extension or be a partial
if (pathinfo($reference, PATHINFO_EXTENSION)) {
$needles = array(
$reference,
self::partialize($reference),
);
} else {
$needles = array(
$reference.'.scss',
$reference.'.sass',
self::partialize($reference).'.scss',
self::partialize($reference).'.sass',
);
}
foreach ($loadPaths as $loadPath) {
foreach ($needles as $needle) {
if (file_exists($file = $loadPath.'/'.$needle)) {
$coll = $factory->createAsset($file, array(), array('root' => $loadPath));
foreach ($coll as $leaf) {
/** @var $leaf AssetInterface */
$leaf->ensureFilter($this);
$children[] = $leaf;
goto next_reference;
}
}
}
}
next_reference:
}
return $children;
}
private static function partialize($reference)
{
$parts = pathinfo($reference);
if ('.' === $parts['dirname']) {
$partial = '_'.$parts['filename'];
} else {
$partial = $parts['dirname'].DIRECTORY_SEPARATOR.'_'.$parts['filename'];
}
if (isset($parts['extension'])) {
$partial .= '.'.$parts['extension'];
}
return $partial;
}
}

View File

@@ -0,0 +1,186 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Exception\FilterException;
use Assetic\Util\FilesystemUtils;
/**
* Loads SASS files.
*
* @link http://sass-lang.com/
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
*/
class SassFilter extends BaseSassFilter
{
const STYLE_NESTED = 'nested';
const STYLE_EXPANDED = 'expanded';
const STYLE_COMPACT = 'compact';
const STYLE_COMPRESSED = 'compressed';
private $sassPath;
private $rubyPath;
private $unixNewlines;
private $scss;
private $style;
private $precision;
private $quiet;
private $debugInfo;
private $lineNumbers;
private $sourceMap;
private $cacheLocation;
private $noCache;
private $compass;
public function __construct($sassPath = '/usr/bin/sass', $rubyPath = null)
{
$this->sassPath = $sassPath;
$this->rubyPath = $rubyPath;
$this->cacheLocation = FilesystemUtils::getTemporaryDirectory();
}
public function setUnixNewlines($unixNewlines)
{
$this->unixNewlines = $unixNewlines;
}
public function setScss($scss)
{
$this->scss = $scss;
}
public function setStyle($style)
{
$this->style = $style;
}
public function setPrecision($precision)
{
$this->precision = $precision;
}
public function setQuiet($quiet)
{
$this->quiet = $quiet;
}
public function setDebugInfo($debugInfo)
{
$this->debugInfo = $debugInfo;
}
public function setLineNumbers($lineNumbers)
{
$this->lineNumbers = $lineNumbers;
}
public function setSourceMap($sourceMap)
{
$this->sourceMap = $sourceMap;
}
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)
{
$sassProcessArgs = array($this->sassPath);
if (null !== $this->rubyPath) {
$sassProcessArgs = array_merge(explode(' ', $this->rubyPath), $sassProcessArgs);
}
$pb = $this->createProcessBuilder($sassProcessArgs);
if ($dir = $asset->getSourceDirectory()) {
$pb->add('--load-path')->add($dir);
}
if ($this->unixNewlines) {
$pb->add('--unix-newlines');
}
if (true === $this->scss || (null === $this->scss && 'scss' == pathinfo($asset->getSourcePath(), PATHINFO_EXTENSION))) {
$pb->add('--scss');
}
if ($this->style) {
$pb->add('--style')->add($this->style);
}
if ($this->precision) {
$pb->add('--precision')->add($this->precision);
}
if ($this->quiet) {
$pb->add('--quiet');
}
if ($this->debugInfo) {
$pb->add('--debug-info');
}
if ($this->lineNumbers) {
$pb->add('--line-numbers');
}
if ($this->sourceMap) {
$pb->add('--sourcemap');
}
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 = FilesystemUtils::createTemporaryFile('sass'));
file_put_contents($input, $asset->getContent());
$proc = $pb->getProcess();
$code = $proc->run();
unlink($input);
if (0 !== $code) {
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
}
$asset->setContent($proc->getOutput());
}
public function filterDump(AssetInterface $asset)
{
}
}

View File

@@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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.
*
* @link http://sass-lang.com/
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
*/
class ScssFilter extends SassFilter
{
public function __construct($sassPath = '/usr/bin/sass', $rubyPath = null)
{
parent::__construct($sassPath, $rubyPath);
$this->setScss(true);
}
}

View File

@@ -0,0 +1,132 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2015 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\Factory\AssetFactory;
use Assetic\Asset\AssetInterface;
use Assetic\Filter\DependencyExtractorInterface;
use Assetic\Util\CssUtils;
/**
* Compiles Sass to CSS.
*
* @author Mikey Clarke <mikey.clarke@me.com>
*/
class SassphpFilter implements DependencyExtractorInterface
{
private $includePaths = array();
private $outputStyle;
public function filterLoad(AssetInterface $asset)
{
$sass = new \Sass();
$includePaths = array_merge(
array($asset->getSourceDirectory()),
$this->includePaths
);
$sass->setIncludePath(implode(':', $includePaths));
if ($this->outputStyle) {
$sass->setStyle($this->outputStyle);
}
$css = $sass->compile($asset->getContent());
$asset->setContent($css);
}
public function filterDump(AssetInterface $asset)
{
}
public function setOutputStyle($outputStyle)
{
$this->outputStyle = $outputStyle;
}
public function setIncludePaths(array $paths)
{
$this->includePaths = $paths;
}
public function addIncludePath($path)
{
$this->includePaths[] = $path;
}
public function getChildren(AssetFactory $factory, $content, $loadPath = null)
{
$children = array();
$includePaths = $this->includePaths;
if (null !== $loadPath && !in_array($loadPath, $includePaths)) {
array_unshift($includePaths, $loadPath);
}
if (empty($includePaths)) {
return $children;
}
foreach (CssUtils::extractImports($content) as $reference) {
if ('.css' === substr($reference, -4)) {
continue;
}
// the reference may or may not have an extension or be a partial
if (pathinfo($reference, PATHINFO_EXTENSION)) {
$needles = array(
$reference,
$this->partialize($reference),
);
} else {
$needles = array(
$reference . '.scss',
$this->partialize($reference) . '.scss',
);
}
foreach ($includePaths as $includePath) {
foreach ($needles as $needle) {
if (file_exists($file = $includePath . '/' . $needle)) {
$child = $factory->createAsset($file, array(), array('root' => $includePath));
$children[] = $child;
$child->load();
$children = array_merge(
$children,
$this->getChildren($factory, $child->getContent(), $includePath)
);
}
}
}
}
return $children;
}
private function partialize($reference)
{
$parts = pathinfo($reference);
if ('.' === $parts['dirname']) {
$partial = '_' . $parts['filename'];
} else {
$partial = $parts['dirname'] . DIRECTORY_SEPARATOR . '_' . $parts['filename'];
}
if (isset($parts['extension'])) {
$partial .= '.' . $parts['extension'];
}
return $partial;
}
}

View File

@@ -0,0 +1,147 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Factory\AssetFactory;
use Assetic\Util\CssUtils;
use Leafo\ScssPhp\Compiler;
/**
* Loads SCSS files using the PHP implementation of scss, scssphp.
*
* Scss files are mostly compatible, but there are slight differences.
*
* @link http://leafo.net/scssphp/
*
* @author Bart van den Burg <bart@samson-it.nl>
*/
class ScssphpFilter implements DependencyExtractorInterface
{
private $compass = false;
private $importPaths = array();
private $customFunctions = array();
private $formatter;
private $variables = array();
public function enableCompass($enable = true)
{
$this->compass = (Boolean) $enable;
}
public function isCompassEnabled()
{
return $this->compass;
}
public function setFormatter($formatter)
{
$legacyFormatters = array(
'scss_formatter' => 'Leafo\ScssPhp\Formatter\Expanded',
'scss_formatter_nested' => 'Leafo\ScssPhp\Formatter\Nested',
'scss_formatter_compressed' => 'Leafo\ScssPhp\Formatter\Compressed',
'scss_formatter_crunched' => 'Leafo\ScssPhp\Formatter\Crunched',
);
if (isset($legacyFormatters[$formatter])) {
@trigger_error(sprintf('The scssphp formatter `%s` is deprecated. Use `%s` instead.', $formatter, $legacyFormatters[$formatter]), E_USER_DEPRECATED);
$formatter = $legacyFormatters[$formatter];
}
$this->formatter = $formatter;
}
public function setVariables(array $variables)
{
$this->variables = $variables;
}
public function addVariable($variable)
{
$this->variables[] = $variable;
}
public function setImportPaths(array $paths)
{
$this->importPaths = $paths;
}
public function addImportPath($path)
{
$this->importPaths[] = $path;
}
public function registerFunction($name, $callable)
{
$this->customFunctions[$name] = $callable;
}
public function filterLoad(AssetInterface $asset)
{
$sc = new Compiler();
if ($this->compass) {
new \scss_compass($sc);
}
if ($dir = $asset->getSourceDirectory()) {
$sc->addImportPath($dir);
}
foreach ($this->importPaths as $path) {
$sc->addImportPath($path);
}
foreach ($this->customFunctions as $name => $callable) {
$sc->registerFunction($name, $callable);
}
if ($this->formatter) {
$sc->setFormatter($this->formatter);
}
if (!empty($this->variables)) {
$sc->setVariables($this->variables);
}
$asset->setContent($sc->compile($asset->getContent()));
}
public function filterDump(AssetInterface $asset)
{
}
public function getChildren(AssetFactory $factory, $content, $loadPath = null)
{
$sc = new Compiler();
if ($loadPath !== null) {
$sc->addImportPath($loadPath);
}
foreach ($this->importPaths as $path) {
$sc->addImportPath($path);
}
$children = array();
foreach (CssUtils::extractImports($content) as $match) {
$file = $sc->findImport($match);
if ($file) {
$children[] = $child = $factory->createAsset($file, array(), array('root' => $loadPath));
$child->load();
$children = array_merge($children, $this->getChildren($factory, $child->getContent(), $loadPath));
}
}
return $children;
}
}

View File

@@ -0,0 +1,47 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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;
/**
* Inserts a separator between assets to prevent merge failures
* e.g. missing semicolon at the end of a JS file
*
* @author Robin McCorkell <rmccorkell@karoshi.org.uk>
*/
class SeparatorFilter implements FilterInterface
{
/**
* @var string
*/
private $separator;
/**
* Constructor.
*
* @param string $separator Separator to use between assets
*/
public function __construct($separator = ';')
{
$this->separator = $separator;
}
public function filterLoad(AssetInterface $asset)
{
}
public function filterDump(AssetInterface $asset)
{
$asset->setContent($asset->getContent() . $this->separator);
}
}

View File

@@ -0,0 +1,152 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Exception\FilterException;
use Assetic\Factory\AssetFactory;
use Assetic\Util\FilesystemUtils;
/**
* 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 extends BaseProcessFilter implements DependencyExtractorInterface
{
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 = FilesystemUtils::createTemporaryFile('sprockets_asset');
file_put_contents($tmpAsset, $asset->getContent());
$input = FilesystemUtils::createTemporaryFile('sprockets_in');
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 = $this->createProcessBuilder(array(
$this->rubyBin,
$input,
));
$proc = $pb->getProcess();
$code = $proc->run();
unlink($tmpAsset);
unlink($input);
if (0 !== $code) {
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
}
$asset->setContent($proc->getOutput());
}
public function filterDump(AssetInterface $asset)
{
}
public function getChildren(AssetFactory $factory, $content, $loadPath = null)
{
// todo
return array();
}
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;
if ($dir = $asset->getSourceDirectory()) {
return sprintf($format, var_export($dir, true));
}
}
}

View File

@@ -0,0 +1,126 @@
<?php
/*
* This file is part of the Assetic package, an OpenSky project.
*
* (c) 2010-2014 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\Exception\FilterException;
use Assetic\Factory\AssetFactory;
use Assetic\Util\FilesystemUtils;
/**
* Loads STYL files.
*
* @link http://learnboost.github.com/stylus/
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
class StylusFilter extends BaseNodeFilter implements DependencyExtractorInterface
{
private $nodeBin;
private $compress;
private $useNib;
/**
* 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->setNodePaths($nodePaths);
}
/**
* Enable output compression.
*
* @param boolean $compress
*/
public function setCompress($compress)
{
$this->compress = $compress;
}
/**
* Enable the use of Nib
*
* @param boolean $useNib
*/
public function setUseNib($useNib)
{
$this->useNib = $useNib;
}
/**
* {@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)%s.render(function(e, css){
if (e) {
throw e;
}
sys.print(css);
process.exit(0);
});
EOF;
// parser options
$parserOptions = array();
if ($dir = $asset->getSourceDirectory()) {
$parserOptions['paths'] = array($dir);
$parserOptions['filename'] = basename($asset->getSourcePath());
}
if (null !== $this->compress) {
$parserOptions['compress'] = $this->compress;
}
$pb = $this->createProcessBuilder();
$pb->add($this->nodeBin)->add($input = FilesystemUtils::createTemporaryFile('stylus'));
file_put_contents($input, sprintf($format,
json_encode($asset->getContent()),
json_encode($parserOptions),
$this->useNib ? '.use(require(\'nib\')())' : ''
));
$proc = $pb->getProcess();
$code = $proc->run();
unlink($input);
if (0 !== $code) {
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
}
$asset->setContent($proc->getOutput());
}
/**
* {@inheritdoc}
*/
public function filterDump(AssetInterface $asset)
{
}
public function getChildren(AssetFactory $factory, $content, $loadPath = null)
{
// todo
return array();
}
}

Some files were not shown because too many files have changed in this diff Show More