diff --git a/autoload.php b/autoload.php index 44247572..455c2afb 100644 --- a/autoload.php +++ b/autoload.php @@ -4,4 +4,4 @@ require_once __DIR__ . '/composer' . '/autoload_real.php'; -return ComposerAutoloaderInit554953bcbd65722da3b85d9b131eb9b1::getLoader(); +return ComposerAutoloaderInite4587901a81fba22cc267be24c196c47::getLoader(); diff --git a/composer/ClassLoader.php b/composer/ClassLoader.php index 1db8d9a0..47ae2ee9 100644 --- a/composer/ClassLoader.php +++ b/composer/ClassLoader.php @@ -42,19 +42,36 @@ */ class ClassLoader { - private $prefixes = array(); - private $fallbackDirs = array(); + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + private $useIncludePath = false; private $classMap = array(); public function getPrefixes() { - return call_user_func_array('array_merge', $this->prefixes); + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; } public function getFallbackDirs() { - return $this->fallbackDirs; + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; } public function getClassMap() @@ -75,23 +92,24 @@ public function addClassMap(array $classMap) } /** - * Registers a set of classes, merging with any others previously set. + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. * - * @param string $prefix The classes prefix - * @param array|string $paths The location(s) of the classes - * @param bool $prepend Prepend the location(s) + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories */ public function add($prefix, $paths, $prepend = false) { if (!$prefix) { if ($prepend) { - $this->fallbackDirs = array_merge( + $this->fallbackDirsPsr0 = array_merge( (array) $paths, - $this->fallbackDirs + $this->fallbackDirsPsr0 ); } else { - $this->fallbackDirs = array_merge( - $this->fallbackDirs, + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, (array) $paths ); } @@ -100,38 +118,104 @@ public function add($prefix, $paths, $prepend = false) } $first = $prefix[0]; - if (!isset($this->prefixes[$first][$prefix])) { - $this->prefixes[$first][$prefix] = (array) $paths; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; return; } if ($prepend) { - $this->prefixes[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-0 base directories + * @param bool $prepend Whether to prepend the directories + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( (array) $paths, - $this->prefixes[$first][$prefix] + $this->prefixDirsPsr4[$prefix] ); } else { - $this->prefixes[$first][$prefix] = array_merge( - $this->prefixes[$first][$prefix], + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], (array) $paths ); } } /** - * Registers a set of classes, replacing any others previously set. + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. * - * @param string $prefix The classes prefix - * @param array|string $paths The location(s) of the classes + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories */ public function set($prefix, $paths) { if (!$prefix) { - $this->fallbackDirs = (array) $paths; + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } - return; + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + */ + public function setPsr4($prefix, $paths) { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; } - $this->prefixes[substr($prefix, 0, 1)][$prefix] = (array) $paths; } /** @@ -182,7 +266,7 @@ public function unregister() public function loadClass($class) { if ($file = $this->findFile($class)) { - include $file; + includeFile($file); return true; } @@ -202,45 +286,79 @@ public function findFile($class) $class = substr($class, 1); } + // class map lookup if (isset($this->classMap[$class])) { return $this->classMap[$class]; } + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . '.php'; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { + if (0 === strpos($class, $prefix)) { + foreach ($this->prefixDirsPsr4[$prefix] as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup if (false !== $pos = strrpos($class, '\\')) { // namespaced class name - $classPath = strtr(substr($class, 0, $pos), '\\', DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; - $className = substr($class, $pos + 1); + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); } else { // PEAR-like class name - $classPath = null; - $className = $class; + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . '.php'; } - $classPath .= strtr($className, '_', DIRECTORY_SEPARATOR) . '.php'; - - $first = $class[0]; - if (isset($this->prefixes[$first])) { - foreach ($this->prefixes[$first] as $prefix => $dirs) { + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { if (0 === strpos($class, $prefix)) { foreach ($dirs as $dir) { - if (file_exists($dir . DIRECTORY_SEPARATOR . $classPath)) { - return $dir . DIRECTORY_SEPARATOR . $classPath; + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; } } } } } - foreach ($this->fallbackDirs as $dir) { - if (file_exists($dir . DIRECTORY_SEPARATOR . $classPath)) { - return $dir . DIRECTORY_SEPARATOR . $classPath; + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; } } - if ($this->useIncludePath && $file = stream_resolve_include_path($classPath)) { + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { return $file; } + // Remember that this class does not exist. return $this->classMap[$class] = false; } } + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/composer/autoload_files.php b/composer/autoload_files.php new file mode 100644 index 00000000..8a19bafe --- /dev/null +++ b/composer/autoload_files.php @@ -0,0 +1,10 @@ + array($vendorDir . '/symfony/process'), 'Sabre\\VObject' => array($vendorDir . '/sabre/vobject/lib'), 'Sabre' => array($vendorDir . '/sabre/dav/lib'), + 'Assetic' => array($vendorDir . '/kriswallsmith/assetic/src'), ); diff --git a/composer/autoload_psr4.php b/composer/autoload_psr4.php new file mode 100644 index 00000000..0c50d0a7 --- /dev/null +++ b/composer/autoload_psr4.php @@ -0,0 +1,9 @@ +set($namespace, $path); } + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + $classMap = require __DIR__ . '/autoload_classmap.php'; if ($classMap) { $loader->addClassMap($classMap); @@ -38,6 +43,16 @@ public static function getLoader() $loader->register(true); + $includeFiles = require __DIR__ . '/autoload_files.php'; + foreach ($includeFiles as $file) { + composerRequiree4587901a81fba22cc267be24c196c47($file); + } + return $loader; } } + +function composerRequiree4587901a81fba22cc267be24c196c47($file) +{ + require $file; +} diff --git a/composer/installed.json b/composer/installed.json index 451e5427..240fc225 100644 --- a/composer/installed.json +++ b/composer/installed.json @@ -118,5 +118,131 @@ "framework", "iCalendar" ] + }, + { + "name": "symfony/process", + "version": "v2.4.2", + "version_normalized": "2.4.2.0", + "target-dir": "Symfony/Component/Process", + "source": { + "type": "git", + "url": "https://github.com/symfony/Process.git", + "reference": "c175448bac997556f8ab972908a4e14c7291fb03" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Process/zipball/c175448bac997556f8ab972908a4e14c7291fb03", + "reference": "c175448bac997556f8ab972908a4e14c7291fb03", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2014-02-11 13:52:09", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\Process\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "http://symfony.com" + }, + { + "name": "kriswallsmith/assetic", + "version": "dev-master", + "version_normalized": "9999999-dev", + "source": { + "type": "git", + "url": "https://github.com/kriswallsmith/assetic.git", + "reference": "d013aae51baf893fe8d38b41f2d24db03e1cfbf7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kriswallsmith/assetic/zipball/d013aae51baf893fe8d38b41f2d24db03e1cfbf7", + "reference": "d013aae51baf893fe8d38b41f2d24db03e1cfbf7", + "shasum": "" + }, + "require": { + "php": ">=5.3.1", + "symfony/process": "~2.1" + }, + "require-dev": { + "cssmin/cssmin": "*", + "joliclic/javascript-packer": "*", + "kamicane/packager": "*", + "leafo/lessphp": "*", + "leafo/scssphp": "*", + "leafo/scssphp-compass": "*", + "mrclay/minify": "*", + "patchwork/jsqueeze": "*", + "phpunit/phpunit": "~3.7", + "ptachoire/cssembed": "*", + "twig/twig": "~1.6" + }, + "suggest": { + "leafo/lessphp": "Assetic provides the integration with the lessphp LESS compiler", + "leafo/scssphp": "Assetic provides the integration with the scssphp SCSS compiler", + "leafo/scssphp-compass": "Assetic provides the integration with the SCSS compass plugin", + "patchwork/jsqueeze": "Assetic provides the integration with the JSqueeze JavaScript compressor", + "ptachoire/cssembed": "Assetic provides the integration with phpcssembed to embed data uris", + "twig/twig": "Assetic provides the integration with the Twig templating engine" + }, + "time": "2014-02-04 10:36:39", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "installation-source": "source", + "autoload": { + "psr-0": { + "Assetic": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kris Wallsmith", + "email": "kris.wallsmith@gmail.com", + "homepage": "http://kriswallsmith.net/" + } + ], + "description": "Asset Management for PHP", + "homepage": "https://github.com/kriswallsmith/assetic", + "keywords": [ + "assets", + "compression", + "minification" + ] } ] diff --git a/kriswallsmith/assetic/.gitattributes b/kriswallsmith/assetic/.gitattributes new file mode 100644 index 00000000..9dca3389 --- /dev/null +++ b/kriswallsmith/assetic/.gitattributes @@ -0,0 +1,7 @@ +/docs export-ignore +/tests export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.travis.yml export-ignore +phpunit.travis.xml export-ignore +phpunit.xml.dist export-ignore \ No newline at end of file diff --git a/kriswallsmith/assetic/.gitignore b/kriswallsmith/assetic/.gitignore new file mode 100644 index 00000000..d5d48fb5 --- /dev/null +++ b/kriswallsmith/assetic/.gitignore @@ -0,0 +1,7 @@ +bin/ +composer.lock +composer.phar +phpunit.xml +vendor/ +node_modules/ +Gemfile.lock diff --git a/kriswallsmith/assetic/.travis.yml b/kriswallsmith/assetic/.travis.yml new file mode 100644 index 00000000..bfba401b --- /dev/null +++ b/kriswallsmith/assetic/.travis.yml @@ -0,0 +1,41 @@ +language: php + +php: + - 5.3 + - 5.4 + - 5.5 + +before_script: + # php deps + - composer self-update + - composer install --dev + + # node deps + - npm install uglify-js@1 && mkdir -p vendor/uglifyjs && mv node_modules vendor/uglifyjs + - npm install + - export UGLIFYJS_BIN=vendor/uglifyjs/node_modules/uglify-js/bin/uglifyjs + + # java deps + - mkdir -p vendor/java + - wget http://dl.google.com/closure-compiler/compiler-latest.zip && unzip compiler-latest.zip -d vendor/java/compiler + - export CLOSURE_JAR=vendor/java/compiler/compiler.jar + - wget http://closure-stylesheets.googlecode.com/files/closure-stylesheets-20111230.jar && mv closure-stylesheets-20111230.jar vendor/java + - export GSS_JAR=vendor/java/closure-stylesheets-20111230.jar + - wget https://github.com/downloads/nzakas/cssembed/cssembed-0.4.5.jar && mv cssembed-0.4.5.jar vendor/java + - export CSSEMBED_JAR=vendor/java/cssembed-0.4.5.jar + - wget http://yui.zenfs.com/releases/yuicompressor/yuicompressor-2.4.7.zip && unzip yuicompressor-2.4.7.zip -d vendor/java + - export YUI_COMPRESSOR_JAR=vendor/java/yuicompressor-2.4.7/build/yuicompressor-2.4.7.jar + + # # ruby deps (why doesn't this work?) + # - bundle install + + # other deps + - sudo apt-get install jpegoptim libjpeg-progs optipng + + - wget -q http://storage.googleapis.com/dart-archive/channels/stable/release/latest/sdk/dartsdk-linux-x64-release.zip && unzip dartsdk-linux-x64-release.zip && mv dart-sdk vendor + - export DART_BIN=vendor/dart-sdk/bin/dart2js + + - wget -q http://static.jonof.id.au/dl/kenutils/pngout-20130221-linux.tar.gz && tar -xzf pngout-20130221-linux.tar.gz && mv pngout-20130221-linux vendor + - export PNGOUT_BIN=vendor/pngout-20130221-linux/x86_64/pngout + +script: ./bin/phpunit -v diff --git a/kriswallsmith/assetic/CHANGELOG-1.0.md b/kriswallsmith/assetic/CHANGELOG-1.0.md new file mode 100644 index 00000000..f91a7e52 --- /dev/null +++ b/kriswallsmith/assetic/CHANGELOG-1.0.md @@ -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 diff --git a/kriswallsmith/assetic/CHANGELOG-1.1.md b/kriswallsmith/assetic/CHANGELOG-1.1.md new file mode 100644 index 00000000..8bcf8bb2 --- /dev/null +++ b/kriswallsmith/assetic/CHANGELOG-1.1.md @@ -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 diff --git a/kriswallsmith/assetic/CHANGELOG-1.2.md b/kriswallsmith/assetic/CHANGELOG-1.2.md new file mode 100644 index 00000000..7ae19569 --- /dev/null +++ b/kriswallsmith/assetic/CHANGELOG-1.2.md @@ -0,0 +1,9 @@ +1.2-dev +------- + + * [BC BREAK] Added `AssetFactory` instance as second argument for + `WorkerInterface::process()` + * [BC BREAK] Removed `LazyAssetManager` from `CacheBustingWorker` constructor + * A new `getSourceDirectory()` method was added on the AssetInterface + * added CssUtils::filterCommentless() + * [BC BREAK] Removed limit and count arguments from CssUtils functions diff --git a/kriswallsmith/assetic/Gemfile b/kriswallsmith/assetic/Gemfile new file mode 100644 index 00000000..1c532499 --- /dev/null +++ b/kriswallsmith/assetic/Gemfile @@ -0,0 +1,5 @@ +source "https://rubygems.org" + +gem "sprockets", "~> 1.0.0" +gem "sass" +gem "compass" diff --git a/kriswallsmith/assetic/LICENSE b/kriswallsmith/assetic/LICENSE new file mode 100644 index 00000000..34342db0 --- /dev/null +++ b/kriswallsmith/assetic/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010-2014 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. diff --git a/kriswallsmith/assetic/README.md b/kriswallsmith/assetic/README.md new file mode 100644 index 00000000..1b544da7 --- /dev/null +++ b/kriswallsmith/assetic/README.md @@ -0,0 +1,340 @@ +# 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 +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 +dump(); +``` + +The filters applied to the collection will cascade to each asset leaf if you +iterate over it. + +``` php +dump(); +} +``` + +The core provides the following filters in the `Assetic\Filter` namespace: + + * `CoffeeScriptFilter`: compiles CoffeeScript into Javascript + * `CompassFilter`: Compass CSS authoring framework + * `CssEmbedFilter`: embeds image data in your stylesheets + * `CssImportFilter`: inlines imported stylesheets + * `CssMinFilter`: minifies CSS + * `CssRewriteFilter`: fixes relative URLs in CSS assets when moving to a new URL + * `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 + * `JSqueeze`: 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 + * `Sass\SassFilter`: parses SASS into CSS + * `Sass\ScssFilter`: parses SCSS into CSS + * `ScssphpFilter`: parses SCSS using scssphp + * `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 +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 +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 +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 +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 +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 +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 +dump(); +$js->dump(); +$js->dump(); +``` + +Twig +---- + +To use the Assetic [Twig][3] extension you must register it to your Twig +environment: + +``` php +addExtension(new AsseticExtension($factory, $debug)); +``` + +Once in place, the extension exposes a stylesheets and a javascripts tag with a syntax similar +to what the asset factory uses: + +``` html+jinja +{% stylesheets '/path/to/sass/main.sass' filter='sass,?yui_css' output='css/all.css' %} + +{% 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 +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 diff --git a/kriswallsmith/assetic/composer.json b/kriswallsmith/assetic/composer.json new file mode 100644 index 00000000..669c20eb --- /dev/null +++ b/kriswallsmith/assetic/composer.json @@ -0,0 +1,83 @@ +{ + "name": "kriswallsmith/assetic", + "minimum-stability": "dev", + "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" + }, + "require-dev": { + "phpunit/phpunit": "~3.7", + "twig/twig": "~1.6", + "leafo/lessphp": "*", + "leafo/scssphp": "*", + "ptachoire/cssembed": "*", + "leafo/scssphp-compass": "*", + + "cssmin/cssmin": "*", + "mrclay/minify": "*", + "kamicane/packager": "*", + "joliclic/javascript-packer": "*", + "patchwork/jsqueeze": "*" + }, + "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.2-dev" + } + }, + "repositories": [ + { + "type": "package", + "package": { + "name": "cssmin/cssmin", + "version": "3.0.1", + "dist": { "url": "http://cssmin.googlecode.com/files/cssmin-v3.0.1.php", "type": "file" }, + "autoload": { "classmap": [ "cssmin-v3.0.1.php" ] } + } + }, + { + "type": "package", + "package": { + "name": "kamicane/packager", + "version": "1.0", + "dist": { "url": "https://github.com/kamicane/packager/archive/1.0.zip", "type": "zip" }, + "autoload": { "classmap": [ "." ] } + } + }, + { + "type": "package", + "package": { + "name": "joliclic/javascript-packer", + "version": "1.1", + "dist": { "url": "http://joliclic.free.fr/php/javascript-packer/telechargement.php?id=2&action=telecharger", "type": "zip" }, + "autoload": { "classmap": [ "class.JavaScriptPacker.php" ] } + } + } + ] +} diff --git a/kriswallsmith/assetic/docs/en/build.md b/kriswallsmith/assetic/docs/en/build.md new file mode 100644 index 00000000..d560172c --- /dev/null +++ b/kriswallsmith/assetic/docs/en/build.md @@ -0,0 +1,32 @@ +Building and Dumping Assets +--------------------------- + +The is the simplest approach to using Assetic. It involves two steps: + + 1. Create a PHP script in your web directory that uses the Assetic OOP API to + create and output an asset. + 2. Reference that file from your template. + +For example, you could create a file in your web directory at +`assets/javascripts.php` with the following code: + + use Assetic\Asset\AssetCollection; + use Assetic\Asset\FileAsset; + use Assetic\Filter\Yui\JsCompressorFilter as YuiCompressorFilter; + + $js = new AssetCollection(array( + new FileAsset(__DIR__.'/jquery.js'), + new FileAsset(__DIR__.'/application.js'), + ), array( + new YuiCompressorFilter('/path/to/yuicompressor.jar'), + )); + + header('Content-Type: application/js'); + echo $js->dump(); + +In your HTML template you would include this generated Javascript using a +simple ` + +Next: [Basic Concepts](concepts.md) diff --git a/kriswallsmith/assetic/docs/en/concepts.md b/kriswallsmith/assetic/docs/en/concepts.md new file mode 100644 index 00000000..7af7b1f2 --- /dev/null +++ b/kriswallsmith/assetic/docs/en/concepts.md @@ -0,0 +1,129 @@ +In order to use the Assetic OOP API you must first understand the two central +concepts of Assetic: assets and filters. + +### What is an Asset? + +As asset is an object that has content and metadata which can be loaded and +dumped. Your assets will probably fall into three categories: Javascripts, +stylesheets and images. Most assets will be loaded from files in your +filesystem, but they can also be loaded via HTTP, a database, from a string, +or virtually anything else. All that an asset has to do is fulfill Assetic's +basic asset interface. + +### What is a Filter? + +A filter is an object that acts upon an asset's content when that asset is +loaded and/or dumped. Similar to assets, a filter can do virtually anything, +as long as it implements Assetic's filter interface. + +Here is a list of some of the tools that can be applied to assets using a +filter: + + * CoffeeScript + * CssEmbed + * CssMin + * Google Closure Compiler + * jpegoptim + * jpegtran + * Less + * LessPHP + * optipng + * Packager + * pngout + * SASS + * Sprockets (version 1) + * Stylus + * YUI Compressor + +### Using Assets and Filters + +You need to start by creating an asset object. This will probably mean +instantiating a `FileAsset` instance, which takes a filesystem path as its +first argument: + + $asset = new Assetic\Asset\FileAsset('/path/to/main.css'); + +Once you have an asset you can begin adding filters to it by calling +`ensureFilter()`. For example, you can add a filter that applies the YUI +Compressor to the contents of the asset: + + $yui = new Assetic\Filter\Yui\CssCompressorFilter('/path/to/yui.jar'); + $asset->ensureFilter($yui); + +Once you've added as many filters as you'd like you can output the finished +asset to the browser: + + header('Content-Type: text/css'); + echo $asset->dump(); + +### Asset Collections + +It is a good idea to combine assets of the same type into a single file to +avoid unnecessary HTTP requests. You can do this in Assetic using the +`AssetCollection` class. This class is just like any other asset in Assetic's +eyes as it implements the asset interface, but under the hood it allows you to +combine multiple assets into one. + + use Assetic\Asset\AssetCollection; + + $asset = new AssetCollection(array( + new FileAsset('/path/to/js/jquery.js'), + new FileAsset('/path/to/js/jquery.plugin.js'), + new FileAsset('/path/to/js/application.js'), + )); + +### Nested Asset Collections + +The collection class implements the asset interface and all assets passed into +a collection must implement the same interface, which means you can easily +nest collections within one another: + + use Assetic\Asset\AssetCollection; + use Assetic\Asset\GlobAsset; + use Assetic\Asset\HttpAsset; + + $asset = new AssetCollection(array( + new HttpAsset('http://example.com/jquery.min.js'), + new GlobAsset('/path/to/js/*'), + )); + +The `HttpAsset` class is a special asset class that loads a file over HTTP; +`GlobAsset` is a special asset collection class that loads files based on a +filesystem glob -- both implement the asset interface. + +This concept of nesting asset collection become even more powerful when you +start applying different sets of filters to each collection. Imagine some of +your application's stylesheets are written in SASS, while some are written in +vanilla CSS. You can combine all of these into one seamless CSS asset: + + use Assetic\Asset\AssetCollection; + use Assetic\Asset\GlobAsset; + use Assetic\Filter\SassFilter; + use Assetic\Filter\Yui\CssCompressorFilter; + + $css = new AssetCollection(array( + new GlobAsset('/path/to/sass/*.sass', array(new SassFilter())), + new GlobAsset('/path/to/css/*.css'), + ), array( + new YuiCompressorFilter('/path/to/yuicompressor.jar'), + )); + +You'll notice I've also applied the YUI compressor filter to the combined +asset so all CSS will be minified. + +### Iterating over an Asset Collection + +Once you have an asset collection you can iterate over it like you would a +plain old PHP array: + + echo "Source paths:\n"; + foreach ($collection as $asset) { + echo ' - '.$asset->getSourcePath()."\n"; + } + +The asset collection iterates recursively, which means you will only see the +"leaf" assets during iteration. Iteration also includes a smart filter which +ensures you only see each asset once, even if the same asset has been included +multiple times. + +Next: [Defining Assets "On The Fly"](define.md) diff --git a/kriswallsmith/assetic/docs/en/define.md b/kriswallsmith/assetic/docs/en/define.md new file mode 100644 index 00000000..3d8d3b13 --- /dev/null +++ b/kriswallsmith/assetic/docs/en/define.md @@ -0,0 +1,145 @@ +Defining Assets "On The Fly" +---------------------------- + +The second approach to using Assetic involves defining your application's +assets "on the fly" in your templates, instead of in an isolated PHP file. +Using this approach, your PHP template would look something like this: + + + +This call to `assetic_javascripts()` serves a dual purpose. It will be read by +the Assetic "formula loader" which will extract an asset "formula" that can be +used to build, dump and output the asset. It will also be executed when the +template is rendered, at which time the path to the output asset is output. + +Assetic includes the following templating helper functions: + + * `assetic_image()` + * `assetic_javascripts()` + * `assetic_stylesheets()` + +Defining assets on the fly is a much more sophisticated technique and +therefore relies on services to do the heavy lifting. The main one being the +asset factory. + +### Asset Factory + +The asset factory knows how to create asset objects using only arrays and +scalar values as input. This is the same string syntax used by the `assetic_*` +template helper functions. + + use Assetic\Factory\AssetFactory; + + $factory = new AssetFactory('/path/to/web'); + $js = $factory->createAsset(array( + 'js/jquery.js', + 'js/jquery.plugin.js', + 'js/application.js', + )); + +### Filter Manager + +You can also apply filters to asset created by the factory. To do this you +must setup a `FilterManager`, which organizes filters by a name. + + use Assetic\FilterManager; + use Assetic\Filter\GoogleClosure\ApiFilter as ClosureFilter; + + $fm = new FilterManager(); + $fm->set('closure', new ClosureFilter()); + $factory->setFilterManager($fm); + + $js = $factory->createAsset('js/*', 'closure'); + +This code creates an instance of the Google Closure Compiler filter and +assigns it the name `closure` using a filter manager. This filter manager is +then injected into the asset factory, making the filter available as `closure` +when creating assets. + +### Debug Mode + +The asset factory also introduces the concept of a debug mode. This mode +allows you to omit certain filters from assets the factory creates depending +on whether it is enabled or not. + +For example, the YUI Compressor is awesome, but it is only appropriate in a +production environment as it is very difficult to debug minified Javascript. + + use Asset\Factory\AssetFactory; + + $factory = new AssetFactory('/path/to/web', true); // debug mode is on + $factory->setFilterManager($fm); + $js = $factory->createAsset('js/*', '?closure'); + +By prefixing the `closure` filter's name with a question mark, we are telling +the factory this filter is optional and should only be applied with debug mode +is off. + +### Asset Manager and Asset References + +The asset factory provides another special string syntax that allows you to +reference assets you defined elsewhere. These are called "asset references" +and involve an asset manager which, similar to the filter manager, organizes +assets by name. + + use Assetic\AssetManager; + use Assetic\Asset\FileAsset; + use Assetic\Factory\AssetFactory; + + $am = new AssetManager(); + $am->set('jquery', new FileAsset('/path/to/jquery.js')); + + $factory = new AssetFactory('/path/to/web'); + $factory->setAssetManager($am); + + $js = $factory->createAsset(array( + '@jquery', + 'js/application.js', + )); + +### Extracting Assets from Templates + +Once you've defined a set of assets in your templates you must use the +"formula loader" service to extract these asset definitions. + + use Assetic\Factory\Loader\FunctionCallsFormulaLoader; + use Assetic\Factory\Resource\FileResource; + + $loader = new FunctionCallsFormulaLoader($factory); + $formulae = $loader->load(new FileResource('/path/to/template.php')); + +These asset formulae aren't much use by themselves. They each include just +enough information for the asset factory to create the intended asset object. +In order for these to be useful they must be wrapped in the special +`LazyAssetManager`. + +### The Lazy Asset Manager + +This service is a composition of the asset factory and one or more formula +loaders. It acts as the glue between these services behind the scenes, but can +be used just like a normal asset manager on the surface. + + use Assetic\Asset\FileAsset; + use Assetic\Factory\LazyAssetManager; + use Assetic\Factory\Loader\FunctionCallsFormulaLoader; + use Assetic\Factory\Resource\DirectoryResource; + + $am = new LazyAssetManager($factory); + $am->set('jquery', new FileAsset('/path/to/jquery.js')); + $am->setLoader('php', new FunctionCallsFormulaLoader($factory)); + $am->addResource(new DirectoryResource('/path/to/templates', '/\.php$/'), 'php'); + +### Asset Writer + +Finally, once you've create an asset manager that knows about every asset +you've defined in your templates, you must use an asset writer to actually +create the files your templates are going to be referencing. + + use Assetic\AssetWriter; + + $writer = new AssetWriter('/path/to/web'); + $writer->writeManagerAssets($am); + +After running this script, all of the assets in your asset manager will be +loaded into memory, filtered with their configured filters and dumped to your +web directory as static files, ready to be served. diff --git a/kriswallsmith/assetic/docs/en/index.md b/kriswallsmith/assetic/docs/en/index.md new file mode 100644 index 00000000..46dc590f --- /dev/null +++ b/kriswallsmith/assetic/docs/en/index.md @@ -0,0 +1,7 @@ +Table Of Contents +----------------- + + 1. [Introduction](introduction.md) + 2. [Building and Dumping Assets](build.md) + 3. [Basic Concepts](concepts.md) + 4. [Defining Assets "On The Fly"](define.md) diff --git a/kriswallsmith/assetic/docs/en/introduction.md b/kriswallsmith/assetic/docs/en/introduction.md new file mode 100644 index 00000000..352f4961 --- /dev/null +++ b/kriswallsmith/assetic/docs/en/introduction.md @@ -0,0 +1,21 @@ +What is Assetic? +---------------- + +Assetic is an asset management framework for PHP 5.3. Assetic enables you to +use a variety of third party tools that will help bring order to your +application's Javascripts, stylesheets and images. + +How Do I Use Assetic? +--------------------- + +There are two distinct approaches you can take when using Assetic: + + 1. Build, dump and output assets in PHP files that you reference directly + from your templates + 2. Defining assets in your templates ("on the fly") and use a loader to + extract, dump and output them + +The first approach is simpler, but the second, with all its moving parts, +offers more flexibility and opportunity for optimization. + +Next: [Building and Dumping Assets](build.md) diff --git a/kriswallsmith/assetic/docs/ja/build.md b/kriswallsmith/assetic/docs/ja/build.md new file mode 100644 index 00000000..bee59ba6 --- /dev/null +++ b/kriswallsmith/assetic/docs/ja/build.md @@ -0,0 +1,30 @@ +アセットのビルドとダンプ +--------------------------- + +Asseticを使う一番単純な方法は、次の2ステップからなります。 + + 1. 公開領域内にPHPスクリプトを作成し、Assetic OOP APIを使用してアセットの作成・出力を行う + 2. テンプレートから上記のファイルを参照する + +例えば、公開領域内に`assets/javascripts.php`ファイルを作成し、 +下記のようなコードを記述します。 + + use Assetic\Asset\AssetCollection; + use Assetic\Asset\FileAsset; + use Assetic\Filter\Yui\JsCompressorFilter as YuiCompressorFilter; + + $js = new AssetCollection(array( + new FileAsset(__DIR__.'/jquery.js'), + new FileAsset(__DIR__.'/application.js'), + ), array( + new YuiCompressorFilter('/path/to/yuicompressor.jar'), + )); + + header('Content-Type: application/js'); + echo $js->dump(); + +HTMLテンプレート側では、単に` + +Next: [コンセプト](concepts.md) diff --git a/kriswallsmith/assetic/docs/ja/concepts.md b/kriswallsmith/assetic/docs/ja/concepts.md new file mode 100644 index 00000000..3479b20e --- /dev/null +++ b/kriswallsmith/assetic/docs/ja/concepts.md @@ -0,0 +1,121 @@ +Assetic OOP APIを使用するためには、まず、[アセット」と「フィルタ」の2つの重要なコンセプトを理解する必要があります。 + +### アセット + +アセットとは、読み込み、及びダンプが可能な、コンテンツとメタデータを内包しているオブジェクトの事を指します。 +大体の場合において3つのカテゴリー、すなわち、Javascriptとスタイルシート、画像のどれかに属することになるでしょう。 +読み込みの方法としては、ファイルシステムからがほとんどですが、 +HTTPやデータベース経由でも、文字列としてでも読み込みが可能で、事実上あらゆるものが読み込み可能です。 +Asseticのアセットインターフェースを満足させさえすれば良いのです。 + + +### フィルタ + +フィルタは、アセットが読み込まれる、かつ/もしくは、ダンプされる際に、 +アセットコンテンツに対して作用するオブジェクトです。 +アセットと同様に、Asseticのフィルタインターフェースを実装することで、 +どのような作用も可能になります。 + +フィルタを用いて、アセットに適用できるツール群の一覧です。 + + * CoffeeScript + * CssEmbed + * CssMin + * Google Closure Compiler + * jpegoptim + * jpegtran + * Less + * LessPHP + * optipng + * Packager + * pngout + * SASS + * Sprockets (version 1) + * Stylus + * YUI Compressor + + +### アセットとフィルタの使用 + +まずはアセットオブジェクトを作成することから始まります。 +多くの場合は`FileAsset`をインスタンス化し、ファイルシステムのパスを第一引数に渡します。 + + $asset = new Assetic\Asset\FileAsset('/path/to/main.css'); + +アセットオブジェクトを作成したら、`ensureFilter()`を呼び、フィルタを追加します。 +例えば、アセットコンテンツにYUI Compressorを適用してみましょう。 + + $yui = new Assetic\Filter\Yui\CssCompressorFilter('/path/to/yui.jar'); + $asset->ensureFilter($yui); + +任意のフィルタを追加したら、完成したアセットをブラウザに出力してみましょう。 + + header('Content-Type: text/css'); + echo $asset->dump(); + +### アセットコレクション + +1つのファイルに同じ種類のアセットをまとめて、不要なHTTPリクエストを抑えてみるのも良いでしょう。 +Asseticでは`AsseticColletion`クラスを使用することで可能となります。 +Assetic内部的には、このクラス自体は他のアセットと同様に、アセットインターフェースを実装したものですが、 +複数のアセットを1つにまとめることが可能になります。 + + use Assetic\Asset\AssetCollection; + + $asset = new AssetCollection(array( + new FileAsset('/path/to/js/jquery.js'), + new FileAsset('/path/to/js/jquery.plugin.js'), + new FileAsset('/path/to/js/application.js'), + )); + +### ネストしたアセットコレクション + +コレクションクラス自体がアセットインターフェースを実装し、コレクション内のアセットも同様に +アセットインターフェースを実装しているので、簡単にネストすることができます。 + + use Assetic\Asset\AssetCollection; + use Assetic\Asset\GlobAsset; + use Assetic\Asset\HttpAsset; + + $asset = new AssetCollection(array( + new HttpAsset('http://example.com/jquery.min.js'), + new GlobAsset('/path/to/js/*'), + )); + +`HttpAsset`は、HTTP経由でファイルを読み込むアセットクラス。 +`GlobAsset`は、ファイルシステムのglobを基にファイル群を読み込むアセットコレクションクラス。 +両者ともにアセットインターフェースを実装しています。 + +このネストしたアセットコレクションという概念は、コレクションそれぞれに異なる +フィルタ群を適用しようとしたときに、効果を発揮します。 +例えば、スタイルシートがSAASで記述されたものと、vanilla CSSを用いて記述されたものからなる +アプリケーションを考えた場合、次のようにして、全てを1つのシームレスなCSSアセットにまとめることができます。 + + use Assetic\Asset\AssetCollection; + use Assetic\Asset\GlobAsset; + use Assetic\Filter\SassFilter; + use Assetic\Filter\Yui\CssCompressorFilter; + + $css = new AssetCollection(array( + new GlobAsset('/path/to/sass/*.sass', array(new SassFilter())), + new GlobAsset('/path/to/css/*.css'), + ), array( + new YuiCompressorFilter('/path/to/yuicompressor.jar'), + )); + +上記の例では、1つにまとめられたCSSを、さらにYUI compressorフィルタを適用することで、全体を圧縮しています。 + +### アセットコレクションのイテレーション + +アセットコレクションは、旧来のPHP配列のように、イテレートできます。 + + echo "Source paths:\n"; + foreach ($collection as $asset) { + echo ' - '.$asset->getSourcePath()."\n"; + } + +アセットコレクションのイテレーションは再帰的で、「葉」にあたるアセットの取得を行います。 +また、気の利いたフィルタを内蔵しているので、同じアセットがコレクション内に複数存在する場合でも、 +一度だけのインクルードが保証されます。 + +Next: [アセットを「オンザフライ」で定義する](define.md) diff --git a/kriswallsmith/assetic/docs/ja/define.md b/kriswallsmith/assetic/docs/ja/define.md new file mode 100644 index 00000000..3b044361 --- /dev/null +++ b/kriswallsmith/assetic/docs/ja/define.md @@ -0,0 +1,140 @@ +アセットの「オンザフライ」な定義 +---------------------------------------- + +Asseticの使用方法二つ目は、独立したPHPファイルを使用する代わりに、 +テンプレートで「オンザフライ」にアセット定義をする方法です。 +このアプローチでは、PHPテンプレートは下記のようになります。 + + + +`assetic_javascripts()`の呼び出しは2つの目的を兼ねています。 +まず、「フォーミュラローダー」により走査され、アセットの構築、ダンプ、及び出力を行うための「フォーミュラ(処方箋)」が抽出されます。 +また、テンプレートのレンダー時にも実行され、アセットの出力パスが出力されます。 + +Asseticには下記のようなヘルパー関数があります。 + + * `assetic_image()` + * `assetic_javascripts()` + * `assetic_stylesheets()` + +アセットをオンザフライに定義するということは、より高度なテクニックであり、 +そのため、重い仕事をするサービスに依存することになります。 +そのうちの重要なものがアセットファクトリです。 + +### アセットファクトリ + +アセットファクトリは、アセットオブジェクトを、配列とスカラ値のみから、 +どのように作成するのか把握しています。 +`assetic_*`ヘルパー関数で使用する記法と同様のものとなります。 + + use Assetic\Factory\AssetFactory; + + $factory = new AssetFactory('/path/to/web'); + $js = $factory->createAsset(array( + 'js/jquery.js', + 'js/jquery.plugin.js', + 'js/application.js', + )); + +### フィルタマネージャー + +ファクトリによって作成されたアセットに対しても、フィルタを適用することができます。 +そのためには、`FilterManager`を設定して、名前を定義しフィルタを構成します。 + + use Assetic\FilterManager; + use Assetic\Filter\GoogleClosure\ApiFilter as ClosureFilter; + + $fm = new FilterManager(); + $fm->set('closure', new ClosureFilter()); + $factory->setFilterManager($fm); + + $js = $factory->createAsset('js/*', 'closure'); + +上記の例では、Google Closure Compilerフィルタをインスタンス化し、 +フィルタマネージャーを通じて`closure`という名前をつけています。 +このフィルタマネージャーをアセットファクトリに渡すことで、 +アセット作成時には、`closure`という名前でフィルタを使用できるようになります。 + +### デバッグモード + +アセットファクトリは、デバッグモードというコンセプトも取り入れており、 +デバッグモードの設定により、ファクトリが作成するアセットから、 +特定のフィルタを除外することができます。 + +たとえば、YUI Compressorは大変素晴らしいのですが、圧縮されたJavascriptを +デバッグするのは大変難しく、プロダクション環境でのみの使用が適切でしょう。 + + use Asset\Factory\AssetFactory; + + $factory = new AssetFactory('/path/to/web', true); // デバッグモードON + $factory->setFilterManager($fm); + $js = $factory->createAsset('js/*', '?closure'); + +フィルタ名`closure`の前にクエスチョンマークを記述すると、ファクトリに対して、 +このフィルタはオプションであり、 +デバッグモードがOFFの時にのみ適用するように通知することができます。 + +### アセットマネージャーとアセットリファレンス + +アセットファクトリにはもう一つ特別な記法があり、別の場所で定義した +アセットを参照することができるようになります。 +これを「アセットリファレンス」と呼び、アセットマネージャーを通じて、 +フィルタマネージャーと同様の、名前によるアセットの構成が可能です。 + + use Assetic\AssetManager; + use Assetic\Asset\FileAsset; + use Assetic\Factory\AssetFactory; + + $am = new AssetManager(); + $am->set('jquery', new FileAsset('/path/to/jquery.js')); + + $factory = new AssetFactory('/path/to/web'); + $factory->setAssetManager($am); + + $js = $factory->createAsset(array( + '@jquery', + 'js/application.js', + )); + +### テンプレートからのアセット抽出 + +テンプレート内でアセット群を定義したら、「フォーミュラローダー」サービスを使用して、 +アセットの定義を抽出します。 + + use Assetic\Factory\Loader\FunctionCallsFormulaLoader; + use Assetic\Factory\Resource\FileResource; + + $loader = new FunctionCallsFormulaLoader($factory); + $formulae = $loader->load(new FileResource('/path/to/template.php')); + +これらのフォーミュラ自体は、それ自体で使途はあまりなく、 +アセットファクトリが目的のアセットオブジェクトを作成するに足る情報しか持っていません。 +`LazyAssetManager`でラップすることで有益なものとなります。 + +### レイジーなアセットマネージャー + +このサービスは、アセットファクトリと、1つ以上のフォーミュラローダーから成っており、 +裏方のサービス間のグルとして動作しますが、表面上では、通常のアセットマネージャーと同じように使用することができます。 + + use Assetic\Asset\FileAsset; + use Assetic\Factory\LazyAssetManager; + use Assetic\Factory\Loader\FunctionCallsFormulaLoader; + use Assetic\Factory\Resource\DirectoryResource; + + $am = new LazyAssetManager($factory); + $am->set('jquery', new FileAsset('/path/to/jquery.js')); + $am->setLoader('php', new FunctionCallsFormulaLoader($factory)); + $am->addResource(new DirectoryResource('/path/to/templates', '/\.php$/'), 'php'); + +### アセットライター + +作成したアセットマネージャーが、テンプレート内で定義した全てのアセットを把握したら、 +アセットライターを使用して、テンプレートが参照することになる実際のファイルを作成します。 + + use Assetic\AssetWriter; + + $writer = new AssetWriter('/path/to/web'); + $writer->writeManagerAssets($am); + +上記のスクリプトを実行すると、アセットマネージャー内のすべてのアセットがメモリに読み込まれ、 +指定したフィルタが適用された後、公開領域に静的ファイルとしてダンプされ、準備完了となります。 diff --git a/kriswallsmith/assetic/docs/ja/index.md b/kriswallsmith/assetic/docs/ja/index.md new file mode 100644 index 00000000..138280d2 --- /dev/null +++ b/kriswallsmith/assetic/docs/ja/index.md @@ -0,0 +1,7 @@ +目次 +----- + + 1. [イントロダクション](introduction.md) + 2. [アセットの構築とダンプ](build.md) + 3. [コンセプト](concepts.md) + 4. [アセットを「オンザフライ」で定義する](define.md) diff --git a/kriswallsmith/assetic/docs/ja/introduction.md b/kriswallsmith/assetic/docs/ja/introduction.md new file mode 100644 index 00000000..0f45a8ed --- /dev/null +++ b/kriswallsmith/assetic/docs/ja/introduction.md @@ -0,0 +1,18 @@ +Asseticとは +----------------- + +Asseticは、PHP5.3用のアセット管理フレームワークです。 +Asseticを導入することで、Javascriptやスタイルシート、画像をコントロールする +様々なサードパーティー製のツールを使用できるようになります。 + +Asseticの使用方法 +--------------------- + +2つの異なるアプローチがあります。 + + 1. アセットのビルド、ダンプ、出力をPHPファイルで行い、テンプレートからそのファイルを直接参照する方法 + 2. テンプレート内でアセットを(「オンザフライ」に)定義し、抽出やダンプ、出力にローダーを使用する方法 + +前者はいくらかシンプルである一方、後者は動的で柔軟性に富み、最適化が可能となります。 + +Next: [アセットの構築とダンプ](build.md) diff --git a/kriswallsmith/assetic/package.json b/kriswallsmith/assetic/package.json new file mode 100644 index 00000000..d43d3bbe --- /dev/null +++ b/kriswallsmith/assetic/package.json @@ -0,0 +1,13 @@ +{ + "devDependencies": { + "uglifycss": "*", + "coffee-script": "*", + "stylus": "*", + "nib": "*", + "ember-precompile": "*", + "typescript": "*", + "less": "*", + "handlebars": "*", + "uglify-js": "*" + } +} diff --git a/kriswallsmith/assetic/phpunit.xml.dist b/kriswallsmith/assetic/phpunit.xml.dist new file mode 100644 index 00000000..c6d60efd --- /dev/null +++ b/kriswallsmith/assetic/phpunit.xml.dist @@ -0,0 +1,25 @@ + + + + + + ./tests/Assetic/Test/ + + + + + + + + + + + + + + + + ./src/Assetic/ + + + diff --git a/kriswallsmith/assetic/src/Assetic/Asset/AssetCache.php b/kriswallsmith/assetic/src/Assetic/Asset/AssetCache.php new file mode 100644 index 00000000..7ce62b5c --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Asset/AssetCache.php @@ -0,0 +1,174 @@ + + */ +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); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Asset/AssetCollection.php b/kriswallsmith/assetic/src/Assetic/Asset/AssetCollection.php new file mode 100644 index 00000000..3141af58 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Asset/AssetCollection.php @@ -0,0 +1,238 @@ + + */ +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; + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Asset/AssetCollectionInterface.php b/kriswallsmith/assetic/src/Assetic/Asset/AssetCollectionInterface.php new file mode 100644 index 00000000..fe67b778 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Asset/AssetCollectionInterface.php @@ -0,0 +1,59 @@ + + */ +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); +} diff --git a/kriswallsmith/assetic/src/Assetic/Asset/AssetInterface.php b/kriswallsmith/assetic/src/Assetic/Asset/AssetInterface.php new file mode 100644 index 00000000..b1d655cf --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Asset/AssetInterface.php @@ -0,0 +1,166 @@ + + */ +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(); +} diff --git a/kriswallsmith/assetic/src/Assetic/Asset/AssetReference.php b/kriswallsmith/assetic/src/Assetic/Asset/AssetReference.php new file mode 100644 index 00000000..4a7776e9 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Asset/AssetReference.php @@ -0,0 +1,138 @@ + + */ +class AssetReference implements AssetInterface +{ + private $am; + private $name; + private $filters = array(); + + public function __construct(AssetManager $am, $name) + { + $this->am = $am; + $this->name = $name; + } + + public function ensureFilter(FilterInterface $filter) + { + $this->filters[] = $filter; + } + + public function getFilters() + { + $this->flushFilters(); + + return $this->callAsset(__FUNCTION__); + } + + public function clearFilters() + { + $this->filters = array(); + $this->callAsset(__FUNCTION__); + } + + public function load(FilterInterface $additionalFilter = null) + { + $this->flushFilters(); + + return $this->callAsset(__FUNCTION__, array($additionalFilter)); + } + + public function dump(FilterInterface $additionalFilter = null) + { + $this->flushFilters(); + + return $this->callAsset(__FUNCTION__, array($additionalFilter)); + } + + public function getContent() + { + return $this->callAsset(__FUNCTION__); + } + + public function setContent($content) + { + $this->callAsset(__FUNCTION__, array($content)); + } + + public function getSourceRoot() + { + return $this->callAsset(__FUNCTION__); + } + + public function getSourcePath() + { + return $this->callAsset(__FUNCTION__); + } + + public function 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->am->get($this->name); + + return call_user_func_array(array($asset, $method), $arguments); + } + + private function flushFilters() + { + $asset = $this->am->get($this->name); + + while ($filter = array_shift($this->filters)) { + $asset->ensureFilter($filter); + } + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Asset/BaseAsset.php b/kriswallsmith/assetic/src/Assetic/Asset/BaseAsset.php new file mode 100644 index 00000000..093b92a8 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Asset/BaseAsset.php @@ -0,0 +1,181 @@ + + */ +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; + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Asset/FileAsset.php b/kriswallsmith/assetic/src/Assetic/Asset/FileAsset.php new file mode 100644 index 00000000..2a33e080 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Asset/FileAsset.php @@ -0,0 +1,78 @@ + + */ +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); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Asset/GlobAsset.php b/kriswallsmith/assetic/src/Assetic/Asset/GlobAsset.php new file mode 100644 index 00000000..2bcc5850 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Asset/GlobAsset.php @@ -0,0 +1,113 @@ + + */ +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)) { + $this->add(new FileAsset($path, array(), $this->getSourceRoot())); + } + } + } + } + + $this->initialized = true; + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Asset/HttpAsset.php b/kriswallsmith/assetic/src/Assetic/Asset/HttpAsset.php new file mode 100644 index 00000000..cd56761f --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Asset/HttpAsset.php @@ -0,0 +1,79 @@ + + */ +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)); + } + } + } + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Asset/Iterator/AssetCollectionFilterIterator.php b/kriswallsmith/assetic/src/Assetic/Asset/Iterator/AssetCollectionFilterIterator.php new file mode 100644 index 00000000..fae5d134 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Asset/Iterator/AssetCollectionFilterIterator.php @@ -0,0 +1,84 @@ + + */ +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); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Asset/Iterator/AssetCollectionIterator.php b/kriswallsmith/assetic/src/Assetic/Asset/Iterator/AssetCollectionIterator.php new file mode 100644 index 00000000..00e65ca5 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Asset/Iterator/AssetCollectionIterator.php @@ -0,0 +1,127 @@ + + */ +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; + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Asset/StringAsset.php b/kriswallsmith/assetic/src/Assetic/Asset/StringAsset.php new file mode 100644 index 00000000..d9b9a88f --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Asset/StringAsset.php @@ -0,0 +1,55 @@ + + */ +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; + } +} diff --git a/kriswallsmith/assetic/src/Assetic/AssetManager.php b/kriswallsmith/assetic/src/Assetic/AssetManager.php new file mode 100644 index 00000000..9b8ee126 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/AssetManager.php @@ -0,0 +1,89 @@ + + */ +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(); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/AssetWriter.php b/kriswallsmith/assetic/src/Assetic/AssetWriter.php new file mode 100644 index 00000000..4f010a48 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/AssetWriter.php @@ -0,0 +1,94 @@ + + * @author Johannes M. Schmitt + */ +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); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Cache/ApcCache.php b/kriswallsmith/assetic/src/Assetic/Cache/ApcCache.php new file mode 100644 index 00000000..74713f38 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Cache/ApcCache.php @@ -0,0 +1,66 @@ + + */ +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); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Cache/ArrayCache.php b/kriswallsmith/assetic/src/Assetic/Cache/ArrayCache.php new file mode 100644 index 00000000..e87028cd --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Cache/ArrayCache.php @@ -0,0 +1,58 @@ + + */ +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]); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Cache/CacheInterface.php b/kriswallsmith/assetic/src/Assetic/Cache/CacheInterface.php new file mode 100644 index 00000000..be133104 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Cache/CacheInterface.php @@ -0,0 +1,53 @@ + + */ +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); +} diff --git a/kriswallsmith/assetic/src/Assetic/Cache/ConfigCache.php b/kriswallsmith/assetic/src/Assetic/Cache/ConfigCache.php new file mode 100644 index 00000000..e285e0bf --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Cache/ConfigCache.php @@ -0,0 +1,123 @@ + + */ +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("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'; + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Cache/ExpiringCache.php b/kriswallsmith/assetic/src/Assetic/Cache/ExpiringCache.php new file mode 100644 index 00000000..46ef85f4 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Cache/ExpiringCache.php @@ -0,0 +1,60 @@ + + */ +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); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Cache/FilesystemCache.php b/kriswallsmith/assetic/src/Assetic/Cache/FilesystemCache.php new file mode 100644 index 00000000..f8eddfdf --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Cache/FilesystemCache.php @@ -0,0 +1,65 @@ + + */ +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); + } + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Exception/Exception.php b/kriswallsmith/assetic/src/Assetic/Exception/Exception.php new file mode 100644 index 00000000..1bd4accf --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Exception/Exception.php @@ -0,0 +1,21 @@ + + */ +interface Exception +{ +} diff --git a/kriswallsmith/assetic/src/Assetic/Exception/FilterException.php b/kriswallsmith/assetic/src/Assetic/Exception/FilterException.php new file mode 100644 index 00000000..03a230b1 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Exception/FilterException.php @@ -0,0 +1,73 @@ + + */ +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; + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Extension/Twig/AsseticExtension.php b/kriswallsmith/assetic/src/Assetic/Extension/Twig/AsseticExtension.php new file mode 100644 index 00000000..775be408 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Extension/Twig/AsseticExtension.php @@ -0,0 +1,76 @@ +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[$function] = 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'; + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Extension/Twig/AsseticFilterFunction.php b/kriswallsmith/assetic/src/Assetic/Extension/Twig/AsseticFilterFunction.php new file mode 100644 index 00000000..aca1e62b --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Extension/Twig/AsseticFilterFunction.php @@ -0,0 +1,29 @@ +filter = $filter; + + parent::__construct($options); + } + + public function compile() + { + return sprintf('$this->env->getExtension(\'assetic\')->getFilterInvoker(\'%s\')->invoke', $this->filter); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Extension/Twig/AsseticFilterInvoker.php b/kriswallsmith/assetic/src/Assetic/Extension/Twig/AsseticFilterInvoker.php new file mode 100644 index 00000000..1b70e434 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Extension/Twig/AsseticFilterInvoker.php @@ -0,0 +1,59 @@ + + */ +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(); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Extension/Twig/AsseticNode.php b/kriswallsmith/assetic/src/Assetic/Extension/Twig/AsseticNode.php new file mode 100644 index 00000000..c9d17aa9 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Extension/Twig/AsseticNode.php @@ -0,0 +1,166 @@ + $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("))") + ; + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Extension/Twig/AsseticTokenParser.php b/kriswallsmith/assetic/src/Assetic/Extension/Twig/AsseticTokenParser.php new file mode 100644 index 00000000..8a916948 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Extension/Twig/AsseticTokenParser.php @@ -0,0 +1,153 @@ +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->getLine()), $token->getValue()), $token->getLine()); + } + } + + $stream->expect(\Twig_Token::BLOCK_END_TYPE); + + $body = $this->parser->subparse(array($this, 'testEndTag'), true); + + $stream->expect(\Twig_Token::BLOCK_END_TYPE); + + if ($this->single && 1 < count($inputs)) { + $inputs = array_slice($inputs, -1); + } + + if (!$name) { + $name = $this->factory->generateAssetName($inputs, $filters, $attributes); + } + + $asset = $this->factory->createAsset($inputs, $filters, $attributes + array('name' => $name)); + + return $this->createNode($asset, $body, $inputs, $filters, $name, $attributes, $token->getLine(), $this->getTag()); + } + + public function getTag() + { + return $this->tag; + } + + public function testEndTag(\Twig_Token $token) + { + return $token->test(array('end'.$this->getTag())); + } + + protected function createNode(AssetInterface $asset, \Twig_NodeInterface $body, array $inputs, array $filters, $name, array $attributes = array(), $lineno = 0, $tag = null) + { + return new AsseticNode($asset, $body, $inputs, $filters, $name, $attributes, $lineno, $tag); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Extension/Twig/TwigFormulaLoader.php b/kriswallsmith/assetic/src/Assetic/Extension/Twig/TwigFormulaLoader.php new file mode 100644 index 00000000..deede103 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Extension/Twig/TwigFormulaLoader.php @@ -0,0 +1,99 @@ + + */ +class TwigFormulaLoader implements FormulaLoaderInterface +{ + private $twig; + + public function __construct(\Twig_Environment $twig) + { + $this->twig = $twig; + } + + public function load(ResourceInterface $resource) + { + try { + $tokens = $this->twig->tokenize($resource->getContent(), (string) $resource); + $nodes = $this->twig->parse($tokens); + } catch (\Exception $e) { + return array(); + } + + return $this->loadNode($nodes); + } + + /** + * Loads assets from the supplied node. + * + * @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 \Twig_Node_Expression_Function) { + $name = version_compare(\Twig_Environment::VERSION, '1.2.0-DEV', '<') + ? $node->getNode('name')->getAttribute('name') + : $node->getAttribute('name'); + + if ($this->twig->getFunction($name) instanceof AsseticFilterFunction) { + $arguments = array(); + foreach ($node->getNode('arguments') as $argument) { + $arguments[] = eval('return '.$this->twig->compile($argument).';'); + } + + $invoker = $this->twig->getExtension('assetic')->getFilterInvoker($name); + + $inputs = isset($arguments[0]) ? (array) $arguments[0] : array(); + $filters = $invoker->getFilters(); + $options = array_replace($invoker->getOptions(), isset($arguments[1]) ? $arguments[1] : array()); + + if (!isset($options['name'])) { + $options['name'] = $invoker->getFactory()->generateAssetName($inputs, $filters, $options); + } + + $formulae[$options['name']] = array($inputs, $filters, $options); + } + } + + foreach ($node as $child) { + if ($child instanceof \Twig_Node) { + $formulae += $this->loadNode($child); + } + } + + return $formulae; + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Extension/Twig/TwigResource.php b/kriswallsmith/assetic/src/Assetic/Extension/Twig/TwigResource.php new file mode 100644 index 00000000..7705af4c --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Extension/Twig/TwigResource.php @@ -0,0 +1,54 @@ + + */ +class TwigResource implements ResourceInterface +{ + private $loader; + private $name; + + public function __construct(\Twig_LoaderInterface $loader, $name) + { + $this->loader = $loader; + $this->name = $name; + } + + public function getContent() + { + try { + return $this->loader->getSource($this->name); + } catch (\Twig_Error_Loader $e) { + return ''; + } + } + + public function isFresh($timestamp) + { + try { + return $this->loader->isFresh($this->name, $timestamp); + } catch (\Twig_Error_Loader $e) { + return false; + } + } + + public function __toString() + { + return $this->name; + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Extension/Twig/ValueContainer.php b/kriswallsmith/assetic/src/Assetic/Extension/Twig/ValueContainer.php new file mode 100644 index 00000000..e197224f --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Extension/Twig/ValueContainer.php @@ -0,0 +1,79 @@ + + */ +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(); + } + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Factory/AssetFactory.php b/kriswallsmith/assetic/src/Assetic/Factory/AssetFactory.php new file mode 100644 index 00000000..b31a6f77 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Factory/AssetFactory.php @@ -0,0 +1,424 @@ + + */ +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; + } + } + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Factory/LazyAssetManager.php b/kriswallsmith/assetic/src/Assetic/Factory/LazyAssetManager.php new file mode 100644 index 00000000..bef72e56 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Factory/LazyAssetManager.php @@ -0,0 +1,210 @@ + + */ +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); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Factory/Loader/BasePhpFormulaLoader.php b/kriswallsmith/assetic/src/Assetic/Factory/Loader/BasePhpFormulaLoader.php new file mode 100644 index 00000000..f2f7f45e --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Factory/Loader/BasePhpFormulaLoader.php @@ -0,0 +1,159 @@ + + */ +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('prototypes[$prototype] = array($tokens, $options); + } + + public function load(ResourceInterface $resource) + { + if (!$nbProtos = count($this->prototypes)) { + throw new \LogicException('There are no prototypes registered.'); + } + + $buffers = array_fill(0, $nbProtos, ''); + $bufferLevels = array_fill(0, $nbProtos, 0); + $buffersInWildcard = array(); + + $tokens = token_get_all($resource->getContent()); + $calls = array(); + + while ($token = array_shift($tokens)) { + $current = self::tokenToString($token); + // loop through each prototype (by reference) + foreach (array_keys($this->prototypes) as $i) { + $prototype =& $this->prototypes[$i][0]; + $options = $this->prototypes[$i][1]; + $buffer =& $buffers[$i]; + $level =& $bufferLevels[$i]; + + if (isset($buffersInWildcard[$i])) { + switch ($current) { + case '(': ++$level; break; + case ')': --$level; break; + } + + $buffer .= $current; + + if (!$level) { + $calls[] = array($buffer.';', $options); + $buffer = ''; + unset($buffersInWildcard[$i]); + } + } elseif ($current == self::tokenToString(current($prototype))) { + $buffer .= $current; + if ('*' == self::tokenToString(next($prototype))) { + $buffersInWildcard[$i] = true; + ++$level; + } + } else { + reset($prototype); + unset($buffersInWildcard[$i]); + $buffer = ''; + } + } + } + + $formulae = array(); + foreach ($calls as $call) { + $formulae += call_user_func_array(array($this, 'processCall'), $call); + } + + return $formulae; + } + + private function processCall($call, array $protoOptions = array()) + { + $tmp = tempnam(sys_get_temp_dir(), 'assetic'); + file_put_contents($tmp, implode("\n", array( + '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))); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Factory/Loader/CachedFormulaLoader.php b/kriswallsmith/assetic/src/Assetic/Factory/Loader/CachedFormulaLoader.php new file mode 100644 index 00000000..9ab002b0 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Factory/Loader/CachedFormulaLoader.php @@ -0,0 +1,68 @@ + + */ +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; + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Factory/Loader/FormulaLoaderInterface.php b/kriswallsmith/assetic/src/Assetic/Factory/Loader/FormulaLoaderInterface.php new file mode 100644 index 00000000..fc45e86c --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Factory/Loader/FormulaLoaderInterface.php @@ -0,0 +1,34 @@ + + */ +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); +} diff --git a/kriswallsmith/assetic/src/Assetic/Factory/Loader/FunctionCallsFormulaLoader.php b/kriswallsmith/assetic/src/Assetic/Factory/Loader/FunctionCallsFormulaLoader.php new file mode 100644 index 00000000..58b56e10 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Factory/Loader/FunctionCallsFormulaLoader.php @@ -0,0 +1,53 @@ + + */ +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; + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Factory/Resource/CoalescingDirectoryResource.php b/kriswallsmith/assetic/src/Assetic/Factory/Resource/CoalescingDirectoryResource.php new file mode 100644 index 00000000..6c089002 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Factory/Resource/CoalescingDirectoryResource.php @@ -0,0 +1,112 @@ + + */ +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); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Factory/Resource/DirectoryResource.php b/kriswallsmith/assetic/src/Assetic/Factory/Resource/DirectoryResource.php new file mode 100644 index 00000000..c823de2d --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Factory/Resource/DirectoryResource.php @@ -0,0 +1,133 @@ + + */ +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 + * @access private + */ +class DirectoryResourceIterator extends \RecursiveIteratorIterator +{ + public function current() + { + return new FileResource(parent::current()->getPathname()); + } +} + +/** + * Filters files by a basename pattern. + * + * @author Kris Wallsmith + * @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); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Factory/Resource/FileResource.php b/kriswallsmith/assetic/src/Assetic/Factory/Resource/FileResource.php new file mode 100644 index 00000000..b7760e19 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Factory/Resource/FileResource.php @@ -0,0 +1,47 @@ + + */ +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; + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Factory/Resource/IteratorResourceInterface.php b/kriswallsmith/assetic/src/Assetic/Factory/Resource/IteratorResourceInterface.php new file mode 100644 index 00000000..3357332a --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Factory/Resource/IteratorResourceInterface.php @@ -0,0 +1,21 @@ + + */ +interface IteratorResourceInterface extends ResourceInterface, \IteratorAggregate +{ +} diff --git a/kriswallsmith/assetic/src/Assetic/Factory/Resource/ResourceInterface.php b/kriswallsmith/assetic/src/Assetic/Factory/Resource/ResourceInterface.php new file mode 100644 index 00000000..7eebbd80 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Factory/Resource/ResourceInterface.php @@ -0,0 +1,43 @@ + + */ +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(); +} diff --git a/kriswallsmith/assetic/src/Assetic/Factory/Worker/CacheBustingWorker.php b/kriswallsmith/assetic/src/Assetic/Factory/Worker/CacheBustingWorker.php new file mode 100644 index 00000000..cbb9d4ca --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Factory/Worker/CacheBustingWorker.php @@ -0,0 +1,71 @@ + + */ +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); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Factory/Worker/EnsureFilterWorker.php b/kriswallsmith/assetic/src/Assetic/Factory/Worker/EnsureFilterWorker.php new file mode 100644 index 00000000..80fd44ac --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Factory/Worker/EnsureFilterWorker.php @@ -0,0 +1,61 @@ + + * @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); + } + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Factory/Worker/WorkerInterface.php b/kriswallsmith/assetic/src/Assetic/Factory/Worker/WorkerInterface.php new file mode 100644 index 00000000..e86cc7ba --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Factory/Worker/WorkerInterface.php @@ -0,0 +1,33 @@ + + */ +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); +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/BaseCssFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/BaseCssFilter.php new file mode 100644 index 00000000..8d65271b --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/BaseCssFilter.php @@ -0,0 +1,54 @@ + + */ +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); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/BaseNodeFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/BaseNodeFilter.php new file mode 100644 index 00000000..457f3475 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/BaseNodeFilter.php @@ -0,0 +1,44 @@ +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(':', $this->nodePaths)); + } + + return $pb; + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/BaseProcessFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/BaseProcessFilter.php new file mode 100644 index 00000000..d75c13b0 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/BaseProcessFilter.php @@ -0,0 +1,58 @@ +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); + } + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/CallablesFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/CallablesFilter.php new file mode 100644 index 00000000..74c69e30 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/CallablesFilter.php @@ -0,0 +1,63 @@ + + */ +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(); + } + +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/CoffeeScriptFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/CoffeeScriptFilter.php new file mode 100644 index 00000000..0601fecb --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/CoffeeScriptFilter.php @@ -0,0 +1,72 @@ + + */ +class CoffeeScriptFilter extends BaseNodeFilter +{ + private $coffeeBin; + private $nodeBin; + + // coffee options + private $bare; + + public function __construct($coffeeBin = '/usr/bin/coffee', $nodeBin = null) + { + $this->coffeeBin = $coffeeBin; + $this->nodeBin = $nodeBin; + } + + public function setBare($bare) + { + $this->bare = $bare; + } + + public function filterLoad(AssetInterface $asset) + { + $input = tempnam(sys_get_temp_dir(), 'assetic_coffeescript'); + 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'); + } + + $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) + { + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/CompassFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/CompassFilter.php new file mode 100644 index 00000000..060eb00a --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/CompassFilter.php @@ -0,0 +1,398 @@ + + */ +class CompassFilter extends BaseProcessFilter implements DependencyExtractorInterface +{ + 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; + + // compass configuration file options + private $plugins = array(); + private $loadPaths = 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 = sys_get_temp_dir(); + + if ('cli' !== php_sapi_name()) { + $this->boring = true; + } + } + + public function setScss($scss) + { + $this->scss = $scss; + } + + // sass options setters + public function setUnixNewlines($unixNewlines) + { + $this->unixNewlines = $unixNewlines; + } + + public function setDebugInfo($debugInfo) + { + $this->debugInfo = $debugInfo; + } + + public function setCacheLocation($cacheLocation) + { + $this->cacheLocation = $cacheLocation; + } + + public function setNoCache($noCache) + { + $this->noCache = $noCache; + } + + // compass options setters + public function setForce($force) + { + $this->force = $force; + } + + public function setStyle($style) + { + $this->style = $style; + } + + public function setQuiet($quiet) + { + $this->quiet = $quiet; + } + + public function setBoring($boring) + { + $this->boring = $boring; + } + + public function setNoLineComments($noLineComments) + { + $this->noLineComments = $noLineComments; + } + + public function setImagesDir($imagesDir) + { + $this->imagesDir = $imagesDir; + } + + public function setJavascriptsDir($javascriptsDir) + { + $this->javascriptsDir = $javascriptsDir; + } + + 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 setLoadPaths(array $loadPaths) + { + $this->loadPaths = $loadPaths; + } + + public function addLoadPath($loadPath) + { + $this->loadPaths[] = $loadPath; + } + + 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 filterLoad(AssetInterface $asset) + { + $loadPaths = $this->loadPaths; + if ($dir = $asset->getSourceDirectory()) { + $loadPaths[] = $dir; + } + + // compass does not seems to handle symlink, so we use realpath() + $tempDir = realpath(sys_get_temp_dir()); + + $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 two options are not passed into the config file + // because like this, compass adapts this to be xxx_dir or xxx_path + // whether it's an absolute path or not + if ($this->imagesDir) { + $pb->add('--images-dir')->add($this->imagesDir); + } + + if ($this->javascriptsDir) { + $pb->add('--javascripts-dir')->add($this->javascriptsDir); + } + + // options in config file + $optionsConfig = array(); + + if (!empty($loadPaths)) { + $optionsConfig['additional_import_paths'] = $loadPaths; + } + + if ($this->unixNewlines) { + $optionsConfig['sass_options']['unix_newlines'] = true; + } + + if ($this->debugInfo) { + $optionsConfig['sass_options']['debug_info'] = true; + } + + if ($this->cacheLocation) { + $optionsConfig['sass_options']['cache_location'] = $this->cacheLocation; + } + + if ($this->noCache) { + $optionsConfig['sass_options']['no_cache'] = true; + } + + if ($this->httpPath) { + $optionsConfig['http_path'] = $this->httpPath; + } + + if ($this->httpImagesPath) { + $optionsConfig['http_images_path'] = $this->httpImagesPath; + } + + if ($this->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; + } + + if ($this->fontsDir) { + $optionsConfig['fonts_dir'] = $this->fontsDir; + } + + // 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', sys_get_temp_dir()); + $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) + { + } + + public function getChildren(AssetFactory $factory, $content, $loadPath = null) + { + // todo + return array(); + } + + 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; + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/CssEmbedFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/CssEmbedFilter.php new file mode 100644 index 00000000..6ecd88b9 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/CssEmbedFilter.php @@ -0,0 +1,142 @@ + + */ +class CssEmbedFilter extends BaseProcessFilter implements DependencyExtractorInterface +{ + private $jarPath; + private $javaPath; + private $charset; + private $mhtml; // Enable MHTML mode. + private $mhtmlRoot; // Use as the MHTML root for the file. + private $root; // Prepends 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 = tempnam(sys_get_temp_dir(), 'assetic_cssembed')); + file_put_contents($input, $asset->getContent()); + + $proc = $pb->getProcess(); + $code = $proc->run(); + unlink($input); + + if (0 !== $code) { + throw FilterException::fromProcess($proc)->setInput($asset->getContent()); + } + + $asset->setContent($proc->getOutput()); + } + + public function getChildren(AssetFactory $factory, $content, $loadPath = null) + { + // todo + return array(); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/CssImportFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/CssImportFilter.php new file mode 100644 index 00000000..fbebd7f9 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/CssImportFilter.php @@ -0,0 +1,108 @@ + + */ +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(); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/CssMinFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/CssMinFilter.php new file mode 100644 index 00000000..6f0b0d2b --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/CssMinFilter.php @@ -0,0 +1,72 @@ + + */ +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)); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/CssRewriteFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/CssRewriteFilter.php new file mode 100644 index 00000000..2cc2f0f3 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/CssRewriteFilter.php @@ -0,0 +1,102 @@ + + */ +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); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/DartFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/DartFilter.php new file mode 100644 index 00000000..f0f71386 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/DartFilter.php @@ -0,0 +1,67 @@ +dartBin = $dartBin; + } + + public function filterLoad(AssetInterface $asset) + { + $input = tempnam(sys_get_temp_dir(), 'assetic_dart'); + $output = tempnam(sys_get_temp_dir(), 'assetic_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) { + if (file_exists($output)) { + unlink($output); + } + + throw FilterException::fromProcess($proc)->setInput($asset->getContent()); + } + + if (!file_exists($output)) { + throw new \RuntimeException('Error creating output file.'); + } + + $asset->setContent(file_get_contents($output)); + unlink($output); + } + + public function filterDump(AssetInterface $asset) + { + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/DependencyExtractorInterface.php b/kriswallsmith/assetic/src/Assetic/Filter/DependencyExtractorInterface.php new file mode 100644 index 00000000..6b9a8fba --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/DependencyExtractorInterface.php @@ -0,0 +1,34 @@ + + */ +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); +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/EmberPrecompileFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/EmberPrecompileFilter.php new file mode 100644 index 00000000..8876e222 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/EmberPrecompileFilter.php @@ -0,0 +1,83 @@ + + */ +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)); + + $templateName = basename($asset->getSourcePath()); + + $inputDirPath = sys_get_temp_dir().DIRECTORY_SEPARATOR.uniqid('input_dir'); + $inputPath = $inputDirPath.DIRECTORY_SEPARATOR.$templateName; + $outputPath = tempnam(sys_get_temp_dir(), 'output'); + + mkdir($inputDirPath); + 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) + { + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/FilterCollection.php b/kriswallsmith/assetic/src/Assetic/Filter/FilterCollection.php new file mode 100644 index 00000000..0fcd54e8 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/FilterCollection.php @@ -0,0 +1,82 @@ + + */ +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); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/FilterInterface.php b/kriswallsmith/assetic/src/Assetic/Filter/FilterInterface.php new file mode 100644 index 00000000..797ac68e --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/FilterInterface.php @@ -0,0 +1,36 @@ + + */ +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); +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/GoogleClosure/BaseCompilerFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/GoogleClosure/BaseCompilerFilter.php new file mode 100644 index 00000000..2509f4a2 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/GoogleClosure/BaseCompilerFilter.php @@ -0,0 +1,101 @@ + + */ +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) + { + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/GoogleClosure/CompilerApiFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/GoogleClosure/CompilerApiFilter.php new file mode 100644 index 00000000..1663b88d --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/GoogleClosure/CompilerApiFilter.php @@ -0,0 +1,132 @@ + + */ +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); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/GoogleClosure/CompilerJarFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/GoogleClosure/CompilerJarFilter.php new file mode 100644 index 00000000..64851e9f --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/GoogleClosure/CompilerJarFilter.php @@ -0,0 +1,111 @@ + + */ +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 = tempnam(sys_get_temp_dir(), 'assetic_google_closure_compiler'); + file_put_contents($externs, $this->jsExterns); + $pb->add('--externs')->add($externs); + } + + if (null !== $this->externsUrl) { + $cleanup[] = $externs = tempnam(sys_get_temp_dir(), 'assetic_google_closure_compiler'); + file_put_contents($externs, file_get_contents($this->externsUrl)); + $pb->add('--externs')->add($externs); + } + + if (null !== $this->excludeDefaultExterns) { + $pb->add('--use_only_custom_externs'); + } + + if (null !== $this->formatting) { + $pb->add('--formatting')->add($this->formatting); + } + + if (null !== $this->useClosureLibrary) { + $pb->add('--manage_closure_dependencies'); + } + + if (null !== $this->warningLevel) { + $pb->add('--warning_level')->add($this->warningLevel); + } + + 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 = tempnam(sys_get_temp_dir(), 'assetic_google_closure_compiler')); + file_put_contents($input, $asset->getContent()); + + $proc = $pb->getProcess(); + $code = $proc->run(); + array_map('unlink', $cleanup); + + if (0 !== $code) { + throw FilterException::fromProcess($proc)->setInput($asset->getContent()); + } + + $asset->setContent($proc->getOutput()); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/GssFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/GssFilter.php new file mode 100644 index 00000000..dce93c75 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/GssFilter.php @@ -0,0 +1,141 @@ + + */ +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 = tempnam(sys_get_temp_dir(), 'assetic_google_closure_stylesheets_compiler')); + 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) + { + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/HandlebarsFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/HandlebarsFilter.php new file mode 100644 index 00000000..fd2e7d9d --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/HandlebarsFilter.php @@ -0,0 +1,102 @@ + + */ +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)); + + $templateName = basename($asset->getSourcePath()); + + $inputDirPath = sys_get_temp_dir().DIRECTORY_SEPARATOR.uniqid('input_dir'); + $inputPath = $inputDirPath.DIRECTORY_SEPARATOR.$templateName; + $outputPath = tempnam(sys_get_temp_dir(), 'output'); + + mkdir($inputDirPath); + 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) + { + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/HashableInterface.php b/kriswallsmith/assetic/src/Assetic/Filter/HashableInterface.php new file mode 100644 index 00000000..cd3ffda1 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/HashableInterface.php @@ -0,0 +1,27 @@ + + */ +interface HashableInterface +{ + /** + * Generates a hash for the object + * + * @return string Object hash + */ + public function hash(); +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/JSMinFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/JSMinFilter.php new file mode 100644 index 00000000..884656b6 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/JSMinFilter.php @@ -0,0 +1,34 @@ + + */ +class JSMinFilter implements FilterInterface +{ + public function filterLoad(AssetInterface $asset) + { + } + + public function filterDump(AssetInterface $asset) + { + $asset->setContent(\JSMin::minify($asset->getContent())); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/JSMinPlusFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/JSMinPlusFilter.php new file mode 100644 index 00000000..14cd0859 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/JSMinPlusFilter.php @@ -0,0 +1,34 @@ + + */ +class JSMinPlusFilter implements FilterInterface +{ + public function filterLoad(AssetInterface $asset) + { + } + + public function filterDump(AssetInterface $asset) + { + $asset->setContent(\JSMinPlus::minify($asset->getContent())); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/JSqueezeFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/JSqueezeFilter.php new file mode 100644 index 00000000..7b79e8f5 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/JSqueezeFilter.php @@ -0,0 +1,57 @@ + + */ +class JSqueezeFilter implements FilterInterface +{ + private $singleLine = true; + private $keepImportantComments = true; + private $specialVarRx = \JSqueeze::SPECIAL_VAR_RX; + + public function setSingleLine($bool) + { + $this->singleLine = (bool) $bool; + } + + public function setSpecialVarRx($specialVarRx) + { + $this->specialVarRx = $specialVarRx; + } + + public function keepImportantComments($bool) + { + $this->keepImportantComments = (bool) $bool; + } + + public function filterLoad(AssetInterface $asset) + { + } + + public function filterDump(AssetInterface $asset) + { + $parser = new \JSqueeze; + $asset->setContent($parser->squeeze( + $asset->getContent(), + $this->singleLine, + $this->keepImportantComments, + $this->specialVarRx + )); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/JpegoptimFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/JpegoptimFilter.php new file mode 100644 index 00000000..8d00db3d --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/JpegoptimFilter.php @@ -0,0 +1,80 @@ + + */ +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 = tempnam(sys_get_temp_dir(), 'assetic_jpegoptim')); + file_put_contents($input, $asset->getContent()); + + $proc = $pb->getProcess(); + $proc->run(); + + if (false !== strpos($proc->getOutput(), 'ERROR')) { + unlink($input); + throw FilterException::fromProcess($proc)->setInput($asset->getContent()); + } + + $asset->setContent(file_get_contents($input)); + + unlink($input); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/JpegtranFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/JpegtranFilter.php new file mode 100644 index 00000000..0d43ce4e --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/JpegtranFilter.php @@ -0,0 +1,102 @@ + + */ +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 = tempnam(sys_get_temp_dir(), 'assetic_jpegtran')); + file_put_contents($input, $asset->getContent()); + + $proc = $pb->getProcess(); + $code = $proc->run(); + unlink($input); + + if (0 !== $code) { + throw FilterException::fromProcess($proc)->setInput($asset->getContent()); + } + + $asset->setContent($proc->getOutput()); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/LessFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/LessFilter.php new file mode 100644 index 00000000..cefd2dba --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/LessFilter.php @@ -0,0 +1,203 @@ + + */ +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'); + +new(less.Parser)(%s).parse(%s, function(e, tree) { + if (e) { + less.writeError(e); + process.exit(2); + } + + try { + sys.print(tree.toCSS(%s)); + } catch (e) { + less.writeError(e); + process.exit(3); + } +}); + +EOF; + + // 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 = tempnam(sys_get_temp_dir(), 'assetic_less')); + file_put_contents($input, sprintf($format, + json_encode($parserOptions), + json_encode($asset->getContent()), + json_encode($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; + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/LessphpFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/LessphpFilter.php new file mode 100644 index 00000000..fade6a52 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/LessphpFilter.php @@ -0,0 +1,147 @@ + + * @author Kris Wallsmith + */ +class LessphpFilter implements DependencyExtractorInterface +{ + private $presets = array(); + private $formatter; + private $preserveComments; + + /** + * 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; + } + + /** + * @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); + } + + if ($this->formatter) { + $lc->setFormatter($this->formatter); + } + + if (null !== $this->preserveComments) { + $lc->setPreserveComments($this->preserveComments); + } + + $asset->setContent($lc->parse($asset->getContent(), $this->presets)); + } + + 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; + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/OptiPngFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/OptiPngFilter.php new file mode 100644 index 00000000..b0a367c1 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/OptiPngFilter.php @@ -0,0 +1,74 @@ + + */ +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 = tempnam(sys_get_temp_dir(), 'assetic_optipng')); + unlink($output); + + $pb->add($input = tempnam(sys_get_temp_dir(), 'assetic_optipng')); + file_put_contents($input, $asset->getContent()); + + $proc = $pb->getProcess(); + $code = $proc->run(); + + if (0 !== $code) { + unlink($input); + throw FilterException::fromProcess($proc)->setInput($asset->getContent()); + } + + $asset->setContent(file_get_contents($output)); + + unlink($input); + unlink($output); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/PackagerFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/PackagerFilter.php new file mode 100644 index 00000000..2fab76cf --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/PackagerFilter.php @@ -0,0 +1,64 @@ + + */ +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 = <<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) + { + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/PackerFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/PackerFilter.php new file mode 100644 index 00000000..6dbe5f39 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/PackerFilter.php @@ -0,0 +1,56 @@ + + */ +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()); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/PhpCssEmbedFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/PhpCssEmbedFilter.php new file mode 100644 index 00000000..b1ff33f6 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/PhpCssEmbedFilter.php @@ -0,0 +1,52 @@ + + * @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(); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/PngoutFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/PngoutFilter.php new file mode 100644 index 00000000..7e1106c9 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/PngoutFilter.php @@ -0,0 +1,127 @@ + + */ +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 = tempnam(sys_get_temp_dir(), 'assetic_pngout')); + file_put_contents($input, $asset->getContent()); + + $output = tempnam(sys_get_temp_dir(), 'assetic_pngout'); + unlink($output); + $pb->add($output .= '.png'); + + $proc = $pb->getProcess(); + $code = $proc->run(); + + if (0 !== $code) { + unlink($input); + throw FilterException::fromProcess($proc)->setInput($asset->getContent()); + } + + $asset->setContent(file_get_contents($output)); + + unlink($input); + unlink($output); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/RooleFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/RooleFilter.php new file mode 100644 index 00000000..f481b1aa --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/RooleFilter.php @@ -0,0 +1,73 @@ + + */ +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 = tempnam(sys_get_temp_dir(), 'assetic_roole'); + file_put_contents($input, $asset->getContent()); + + $pb = $this->createProcessBuilder($this->nodeBin + ? array($this->nodeBin, $this->rooleBin) + : array($this->rooleBin)); + + $pb->add('-p'); + + $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) + { + } + + public function getChildren(AssetFactory $factory, $content, $loadPath = null) + { + // todo + return array(); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/Sass/SassFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/Sass/SassFilter.php new file mode 100644 index 00000000..edd0ab82 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/Sass/SassFilter.php @@ -0,0 +1,250 @@ + + */ +class SassFilter extends BaseProcessFilter implements DependencyExtractorInterface +{ + 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 $quiet; + private $debugInfo; + private $lineNumbers; + private $loadPaths = array(); + private $cacheLocation; + private $noCache; + private $compass; + + public function __construct($sassPath = '/usr/bin/sass', $rubyPath = null) + { + $this->sassPath = $sassPath; + $this->rubyPath = $rubyPath; + $this->cacheLocation = realpath(sys_get_temp_dir()); + } + + public function setUnixNewlines($unixNewlines) + { + $this->unixNewlines = $unixNewlines; + } + + public function setScss($scss) + { + $this->scss = $scss; + } + + public function setStyle($style) + { + $this->style = $style; + } + + public function setQuiet($quiet) + { + $this->quiet = $quiet; + } + + public function setDebugInfo($debugInfo) + { + $this->debugInfo = $debugInfo; + } + + public function setLineNumbers($lineNumbers) + { + $this->lineNumbers = $lineNumbers; + } + + public function setLoadPaths(array $loadPaths) + { + $this->loadPaths = $loadPaths; + } + + public function addLoadPath($loadPath) + { + $this->loadPaths[] = $loadPath; + } + + public function setCacheLocation($cacheLocation) + { + $this->cacheLocation = $cacheLocation; + } + + public function setNoCache($noCache) + { + $this->noCache = $noCache; + } + + public function setCompass($compass) + { + $this->compass = $compass; + } + + public function filterLoad(AssetInterface $asset) + { + $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->quiet) { + $pb->add('--quiet'); + } + + if ($this->debugInfo) { + $pb->add('--debug-info'); + } + + if ($this->lineNumbers) { + $pb->add('--line-numbers'); + } + + foreach ($this->loadPaths as $loadPath) { + $pb->add('--load-path')->add($loadPath); + } + + if ($this->cacheLocation) { + $pb->add('--cache-location')->add($this->cacheLocation); + } + + if ($this->noCache) { + $pb->add('--no-cache'); + } + + if ($this->compass) { + $pb->add('--compass'); + } + + // input + $pb->add($input = tempnam(sys_get_temp_dir(), 'assetic_sass')); + file_put_contents($input, $asset->getContent()); + + $proc = $pb->getProcess(); + $code = $proc->run(); + unlink($input); + + if (0 !== $code) { + throw FilterException::fromProcess($proc)->setInput($asset->getContent()); + } + + $asset->setContent($proc->getOutput()); + } + + public function filterDump(AssetInterface $asset) + { + } + + public function getChildren(AssetFactory $factory, $content, $loadPath = null) + { + $loadPaths = $this->loadPaths; + if ($loadPath) { + array_unshift($loadPaths, $loadPath); + } + + if (!$loadPaths) { + return array(); + } + + $children = array(); + foreach (CssUtils::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) { + $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; + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/Sass/ScssFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/Sass/ScssFilter.php new file mode 100644 index 00000000..f8be046b --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/Sass/ScssFilter.php @@ -0,0 +1,28 @@ + + */ +class ScssFilter extends SassFilter +{ + public function __construct($sassPath = '/usr/bin/sass', $rubyPath = null) + { + parent::__construct($sassPath, $rubyPath); + + $this->setScss(true); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/ScssphpFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/ScssphpFilter.php new file mode 100644 index 00000000..f1f467c0 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/ScssphpFilter.php @@ -0,0 +1,88 @@ + + */ +class ScssphpFilter implements DependencyExtractorInterface +{ + private $compass = false; + + private $importPaths = array(); + + private $customFunctions = array(); + + public function enableCompass($enable = true) + { + $this->compass = (Boolean) $enable; + } + + public function isCompassEnabled() + { + return $this->compass; + } + + public function filterLoad(AssetInterface $asset) + { + $sc = new \scssc(); + 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); + } + + $asset->setContent($sc->compile($asset->getContent())); + } + + 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 filterDump(AssetInterface $asset) + { + } + + public function getChildren(AssetFactory $factory, $content, $loadPath = null) + { + // todo + return array(); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/SprocketsFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/SprocketsFilter.php new file mode 100644 index 00000000..6edd589e --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/SprocketsFilter.php @@ -0,0 +1,151 @@ + + */ +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 = tempnam(sys_get_temp_dir(), 'assetic_sprockets'); + file_put_contents($tmpAsset, $asset->getContent()); + + $input = tempnam(sys_get_temp_dir(), 'assetic_sprockets'); + file_put_contents($input, sprintf($format, + $this->sprocketsLib + ? sprintf('File.join(%s, \'sprockets\')', var_export($this->sprocketsLib, true)) + : '\'sprockets\'', + $this->getHack($asset), + var_export($tmpAsset, true), + $more + )); + + $pb = $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)); + } + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/StylusFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/StylusFilter.php new file mode 100644 index 00000000..9bdf864b --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/StylusFilter.php @@ -0,0 +1,125 @@ + + */ +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 = tempnam(sys_get_temp_dir(), 'assetic_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(); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/TypeScriptFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/TypeScriptFilter.php new file mode 100644 index 00000000..340880df --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/TypeScriptFilter.php @@ -0,0 +1,76 @@ + + */ +class TypeScriptFilter extends BaseNodeFilter +{ + private $tscBin; + private $nodeBin; + + public function __construct($tscBin = '/usr/bin/tsc', $nodeBin = null) + { + $this->tscBin = $tscBin; + $this->nodeBin = $nodeBin; + } + + public function filterLoad(AssetInterface $asset) + { + $pb = $this->createProcessBuilder($this->nodeBin + ? array($this->nodeBin, $this->tscBin) + : array($this->tscBin)); + + $templateName = basename($asset->getSourcePath()); + + $inputDirPath = sys_get_temp_dir().DIRECTORY_SEPARATOR.uniqid('input_dir'); + $inputPath = $inputDirPath.DIRECTORY_SEPARATOR.$templateName.'.ts'; + $outputPath = tempnam(sys_get_temp_dir(), 'output'); + + mkdir($inputDirPath); + file_put_contents($inputPath, $asset->getContent()); + + $pb->add($inputPath)->add('--out')->add($outputPath); + + $proc = $pb->getProcess(); + $code = $proc->run(); + unlink($inputPath); + rmdir($inputDirPath); + + if (0 !== $code) { + if (file_exists($outputPath)) { + unlink($outputPath); + } + throw FilterException::fromProcess($proc)->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) + { + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/UglifyCssFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/UglifyCssFilter.php new file mode 100644 index 00000000..677127f4 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/UglifyCssFilter.php @@ -0,0 +1,119 @@ + + */ +class UglifyCssFilter extends BaseNodeFilter +{ + private $uglifycssBin; + private $nodeBin; + + private $expandVars; + private $uglyComments; + private $cuteComments; + + /** + * @param string $uglifycssBin Absolute path to the uglifycss executable + * @param string $nodeBin Absolute path to the folder containg node.js executable + */ + public function __construct($uglifycssBin = '/usr/bin/uglifycss', $nodeBin = null) + { + $this->uglifycssBin = $uglifycssBin; + $this->nodeBin = $nodeBin; + } + + /** + * Expand variables + * @param bool $expandVars True to enable + */ + public function setExpandVars($expandVars) + { + $this->expandVars = $expandVars; + } + + /** + * Remove newlines within preserved comments + * @param bool $uglyComments True to enable + */ + public function setUglyComments($uglyComments) + { + $this->uglyComments = $uglyComments; + } + + /** + * Preserve newlines within and around preserved comments + * @param bool $cuteComments True to enable + */ + public function setCuteComments($cuteComments) + { + $this->cuteComments = $cuteComments; + } + + /** + * @see Assetic\Filter\FilterInterface::filterLoad() + */ + public function filterLoad(AssetInterface $asset) + { + } + + /** + * Run the asset through UglifyJs + * + * @see Assetic\Filter\FilterInterface::filterDump() + */ + public function filterDump(AssetInterface $asset) + { + $pb = $this->createProcessBuilder($this->nodeBin + ? array($this->nodeBin, $this->uglifycssBin) + : array($this->uglifycssBin)); + + if ($this->expandVars) { + $pb->add('--expand-vars'); + } + + if ($this->uglyComments) { + $pb->add('--ugly-comments'); + } + + if ($this->cuteComments) { + $pb->add('--cute-comments'); + } + + // 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()); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/UglifyJs2Filter.php b/kriswallsmith/assetic/src/Assetic/Filter/UglifyJs2Filter.php new file mode 100644 index 00000000..7c299861 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/UglifyJs2Filter.php @@ -0,0 +1,135 @@ + + */ +class UglifyJs2Filter extends BaseNodeFilter +{ + private $uglifyjsBin; + private $nodeBin; + private $compress; + private $beautify; + private $mangle; + private $screwIe8; + private $comments; + private $wrap; + + public function __construct($uglifyjsBin = '/usr/bin/uglifyjs', $nodeBin = null) + { + $this->uglifyjsBin = $uglifyjsBin; + $this->nodeBin = $nodeBin; + } + + public function setCompress($compress) + { + $this->compress = $compress; + } + + public function setBeautify($beautify) + { + $this->beautify = $beautify; + } + + public function setMangle($mangle) + { + $this->mangle = $mangle; + } + + public function setScrewIe8($screwIe8) + { + $this->screwIe8 = $screwIe8; + } + + public function setComments($comments) + { + $this->comments = $comments; + } + + public function setWrap($wrap) + { + $this->wrap = $wrap; + } + + public function filterLoad(AssetInterface $asset) + { + } + + public function filterDump(AssetInterface $asset) + { + $pb = $this->createProcessBuilder($this->nodeBin + ? array($this->nodeBin, $this->uglifyjsBin) + : array($this->uglifyjsBin)); + + if ($this->compress) { + $pb->add('--compress'); + } + + if ($this->beautify) { + $pb->add('--beautify'); + } + + if ($this->mangle) { + $pb->add('--mangle'); + } + + if ($this->screwIe8) { + $pb->add('--screw-ie8'); + } + + if ($this->comments) { + $pb->add('--comments')->add(true === $this->comments ? 'all' : $this->comments); + } + + if ($this->wrap) { + $pb->add('--wrap')->add($this->wrap); + } + + // input and output files + $input = tempnam(sys_get_temp_dir(), 'input'); + $output = tempnam(sys_get_temp_dir(), 'output'); + + file_put_contents($input, $asset->getContent()); + $pb->add('-o')->add($output)->add($input); + + $proc = $pb->getProcess(); + $code = $proc->run(); + unlink($input); + + if (0 !== $code) { + if (file_exists($output)) { + unlink($output); + } + + if (127 === $code) { + throw new \RuntimeException('Path to node executable could not be resolved.'); + } + + throw FilterException::fromProcess($proc)->setInput($asset->getContent()); + } + + if (!file_exists($output)) { + throw new \RuntimeException('Error creating output file.'); + } + + $asset->setContent(file_get_contents($output)); + + unlink($output); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/UglifyJsFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/UglifyJsFilter.php new file mode 100644 index 00000000..de77d588 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/UglifyJsFilter.php @@ -0,0 +1,145 @@ + + */ +class UglifyJsFilter extends BaseNodeFilter +{ + private $uglifyjsBin; + private $nodeBin; + + private $noCopyright; + private $beautify; + private $unsafe; + private $mangle; + + /** + * @param string $uglifyjsBin Absolute path to the uglifyjs executable + * @param string $nodeBin Absolute path to the folder containg node.js executable + */ + public function __construct($uglifyjsBin = '/usr/bin/uglifyjs', $nodeBin = null) + { + $this->uglifyjsBin = $uglifyjsBin; + $this->nodeBin = $nodeBin; + } + + /** + * Removes the first block of comments as well + * @param bool $noCopyright True to enable + */ + public function setNoCopyright($noCopyright) + { + $this->noCopyright = $noCopyright; + } + + /** + * Output indented code + * @param bool $beautify True to enable + */ + public function setBeautify($beautify) + { + $this->beautify = $beautify; + } + + /** + * Enable additional optimizations that are known to be unsafe in some situations. + * @param bool $unsafe True to enable + */ + public function setUnsafe($unsafe) + { + $this->unsafe = $unsafe; + } + + /** + * Safely mangle variable and function names for greater file compress. + * @param bool $mangle True to enable + */ + public function setMangle($mangle) + { + $this->mangle = $mangle; + } + + /** + * @see Assetic\Filter\FilterInterface::filterLoad() + */ + public function filterLoad(AssetInterface $asset) + { + } + + /** + * Run the asset through UglifyJs + * + * @see Assetic\Filter\FilterInterface::filterDump() + */ + public function filterDump(AssetInterface $asset) + { + $pb = $this->createProcessBuilder($this->nodeBin + ? array($this->nodeBin, $this->uglifyjsBin) + : array($this->uglifyjsBin)); + + if ($this->noCopyright) { + $pb->add('--no-copyright'); + } + + if ($this->beautify) { + $pb->add('--beautify'); + } + + if ($this->unsafe) { + $pb->add('--unsafe'); + } + + if (false === $this->mangle) { + $pb->add('--no-mangle'); + } + + // input and output files + $input = tempnam(sys_get_temp_dir(), 'input'); + $output = tempnam(sys_get_temp_dir(), 'output'); + + file_put_contents($input, $asset->getContent()); + $pb->add('-o')->add($output)->add($input); + + $proc = $pb->getProcess(); + $code = $proc->run(); + unlink($input); + + if (0 !== $code) { + if (file_exists($output)) { + unlink($output); + } + + if (127 === $code) { + throw new \RuntimeException('Path to node executable could not be resolved.'); + } + + throw FilterException::fromProcess($proc)->setInput($asset->getContent()); + } + + if (!file_exists($output)) { + throw new \RuntimeException('Error creating output file.'); + } + + $uglifiedJs = file_get_contents($output); + unlink($output); + + $asset->setContent($uglifiedJs); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/Yui/BaseCompressorFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/Yui/BaseCompressorFilter.php new file mode 100644 index 00000000..070a2990 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/Yui/BaseCompressorFilter.php @@ -0,0 +1,116 @@ + + */ +abstract class BaseCompressorFilter extends BaseProcessFilter +{ + private $jarPath; + private $javaPath; + private $charset; + private $lineBreak; + private $stackSize; + + public function __construct($jarPath, $javaPath = '/usr/bin/java') + { + $this->jarPath = $jarPath; + $this->javaPath = $javaPath; + } + + public function setCharset($charset) + { + $this->charset = $charset; + } + + public function setLineBreak($lineBreak) + { + $this->lineBreak = $lineBreak; + } + + public function setStackSize($stackSize) + { + $this->stackSize = $stackSize; + } + + public function filterLoad(AssetInterface $asset) + { + } + + /** + * Compresses a string. + * + * @param string $content The content to compress + * @param string $type The type of content, either "js" or "css" + * @param array $options An indexed array of additional options + * + * @return string The compressed content + */ + protected function compress($content, $type, $options = array()) + { + $pb = $this->createProcessBuilder(array($this->javaPath)); + + if (null !== $this->stackSize) { + $pb->add('-Xss'.$this->stackSize); + } + + $pb->add('-jar')->add($this->jarPath); + + foreach ($options as $option) { + $pb->add($option); + } + + if (null !== $this->charset) { + $pb->add('--charset')->add($this->charset); + } + + if (null !== $this->lineBreak) { + $pb->add('--line-break')->add($this->lineBreak); + } + + // input and output files + $tempDir = realpath(sys_get_temp_dir()); + $input = tempnam($tempDir, 'YUI-IN-'); + $output = tempnam($tempDir, 'YUI-OUT-'); + file_put_contents($input, $content); + $pb->add('-o')->add($output)->add('--type')->add($type)->add($input); + + $proc = $pb->getProcess(); + $code = $proc->run(); + unlink($input); + + if (0 !== $code) { + if (file_exists($output)) { + unlink($output); + } + + throw FilterException::fromProcess($proc)->setInput($content); + } + + if (!file_exists($output)) { + throw new \RuntimeException('Error creating output file.'); + } + + $retval = file_get_contents($output); + unlink($output); + + return $retval; + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/Yui/CssCompressorFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/Yui/CssCompressorFilter.php new file mode 100644 index 00000000..077c6d59 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/Yui/CssCompressorFilter.php @@ -0,0 +1,28 @@ + + */ +class CssCompressorFilter extends BaseCompressorFilter +{ + public function filterDump(AssetInterface $asset) + { + $asset->setContent($this->compress($asset->getContent(), 'css')); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Filter/Yui/JsCompressorFilter.php b/kriswallsmith/assetic/src/Assetic/Filter/Yui/JsCompressorFilter.php new file mode 100644 index 00000000..b2a59fb1 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Filter/Yui/JsCompressorFilter.php @@ -0,0 +1,61 @@ + + */ +class JsCompressorFilter extends BaseCompressorFilter +{ + private $nomunge; + private $preserveSemi; + private $disableOptimizations; + + public function setNomunge($nomunge = true) + { + $this->nomunge = $nomunge; + } + + public function setPreserveSemi($preserveSemi) + { + $this->preserveSemi = $preserveSemi; + } + + public function setDisableOptimizations($disableOptimizations) + { + $this->disableOptimizations = $disableOptimizations; + } + + public function filterDump(AssetInterface $asset) + { + $options = array(); + + if ($this->nomunge) { + $options[] = '--nomunge'; + } + + if ($this->preserveSemi) { + $options[] = '--preserve-semi'; + } + + if ($this->disableOptimizations) { + $options[] = '--disable-optimizations'; + } + + $asset->setContent($this->compress($asset->getContent(), 'js', $options)); + } +} diff --git a/kriswallsmith/assetic/src/Assetic/FilterManager.php b/kriswallsmith/assetic/src/Assetic/FilterManager.php new file mode 100644 index 00000000..e2820d99 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/FilterManager.php @@ -0,0 +1,64 @@ + + */ +class FilterManager +{ + private $filters = array(); + + public function set($alias, FilterInterface $filter) + { + $this->checkName($alias); + + $this->filters[$alias] = $filter; + } + + public function get($alias) + { + if (!isset($this->filters[$alias])) { + throw new \InvalidArgumentException(sprintf('There is no "%s" filter.', $alias)); + } + + return $this->filters[$alias]; + } + + public function has($alias) + { + return isset($this->filters[$alias]); + } + + public function getNames() + { + return array_keys($this->filters); + } + + /** + * Checks that a name is valid. + * + * @param string $name An asset name candidate + * + * @throws \InvalidArgumentException If the asset name is invalid + */ + protected function checkName($name) + { + if (!ctype_alnum(str_replace('_', '', $name))) { + throw new \InvalidArgumentException(sprintf('The name "%s" is invalid.', $name)); + } + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Util/CssUtils.php b/kriswallsmith/assetic/src/Assetic/Util/CssUtils.php new file mode 100644 index 00000000..44d0bee5 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Util/CssUtils.php @@ -0,0 +1,136 @@ + + */ +abstract class CssUtils +{ + const REGEX_URLS = '/url\((["\']?)(?P.*?)(\\1)\)/'; + const REGEX_IMPORTS = '/@import (?:url\()?(\'|"|)(?P[^\'"\)\n\r]*)\1\)?;?/'; + const REGEX_IMPORTS_NO_URLS = '/@import (?!url\()(\'|"|)(?P[^\'"\)\n\r]*)\1;?/'; + const REGEX_IE_FILTERS = '/src=(["\']?)(?P.*?)\\1/'; + const REGEX_COMMENTS = '/(\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)/'; + + /** + * Filters all references -- url() and "@import" -- through a callable. + * + * @param string $content The CSS + * @param callable $callback A PHP callable + * + * @return string The filtered CSS + */ + public static function filterReferences($content, $callback) + { + $content = static::filterUrls($content, $callback); + $content = static::filterImports($content, $callback, false); + $content = static::filterIEFilters($content, $callback); + + return $content; + } + + /** + * Filters all CSS url()'s through a callable. + * + * @param string $content The CSS + * @param callable $callback A PHP callable + * + * @return string The filtered CSS + */ + public static function filterUrls($content, $callback) + { + $pattern = static::REGEX_URLS; + + return static::filterCommentless($content, function($part) use(& $callback, $pattern) { + return preg_replace_callback($pattern, $callback, $part); + }); + } + + /** + * Filters all CSS imports through a callable. + * + * @param string $content The CSS + * @param callable $callback A PHP callable + * @param Boolean $includeUrl Whether to include url() in the pattern + * + * @return string The filtered CSS + */ + public static function filterImports($content, $callback, $includeUrl = true) + { + $pattern = $includeUrl ? static::REGEX_IMPORTS : static::REGEX_IMPORTS_NO_URLS; + + return static::filterCommentless($content, function($part) use(& $callback, $pattern) { + return preg_replace_callback($pattern, $callback, $part); + }); + } + + /** + * Filters all IE filters (AlphaImageLoader filter) through a callable. + * + * @param string $content The CSS + * @param callable $callback A PHP callable + * + * @return string The filtered CSS + */ + public static function filterIEFilters($content, $callback) + { + $pattern = static::REGEX_IE_FILTERS; + + return static::filterCommentless($content, function($part) use (& $callback, $pattern) { + return preg_replace_callback($pattern, $callback, $part); + }); + } + + /** + * Filters each non-comment part through a callable. + * + * @param string $content The CSS + * @param callable $callback A PHP callable + * + * @return string The filtered CSS + */ + public static function filterCommentless($content, $callback) + { + $result = ''; + foreach (preg_split(static::REGEX_COMMENTS, $content, -1, PREG_SPLIT_DELIM_CAPTURE) as $part) { + if (!preg_match(static::REGEX_COMMENTS, $part, $match) || $part != $match[0]) { + $part = call_user_func($callback, $part); + } + + $result .= $part; + } + + return $result; + } + + /** + * Extracts all references from the supplied CSS content. + * + * @param string $content The CSS content + * + * @return array An array of unique URLs + */ + public static function extractImports($content) + { + $imports = array(); + static::filterImports($content, function($matches) use(& $imports) { + $imports[] = $matches['url']; + }); + + return array_unique($imports); + } + + final private function __construct() { } +} diff --git a/kriswallsmith/assetic/src/Assetic/Util/LessUtils.php b/kriswallsmith/assetic/src/Assetic/Util/LessUtils.php new file mode 100644 index 00000000..6c9c4b44 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Util/LessUtils.php @@ -0,0 +1,24 @@ + + */ +abstract class LessUtils extends CssUtils +{ + const REGEX_IMPORTS = '/@import(?:-once)? (?:url\()?(\'|"|)(?P[^\'"\)\n\r]*)\1\)?;?/'; + const REGEX_IMPORTS_NO_URLS = '/@import(?:-once)? (?!url\()(\'|"|)(?P[^\'"\)\n\r]*)\1;?/'; + const REGEX_COMMENTS = '/((?:\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)|\/\/[^\n]+)/'; +} diff --git a/kriswallsmith/assetic/src/Assetic/Util/TraversableString.php b/kriswallsmith/assetic/src/Assetic/Util/TraversableString.php new file mode 100644 index 00000000..400e97c6 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Util/TraversableString.php @@ -0,0 +1,44 @@ + + */ +class TraversableString implements \IteratorAggregate, \Countable +{ + private $one; + private $many; + + public function __construct($one, array $many) + { + $this->one = $one; + $this->many = $many; + } + + public function getIterator() + { + return new \ArrayIterator($this->many); + } + + public function count() + { + return count($this->many); + } + + public function __toString() + { + return (string) $this->one; + } +} diff --git a/kriswallsmith/assetic/src/Assetic/Util/VarUtils.php b/kriswallsmith/assetic/src/Assetic/Util/VarUtils.php new file mode 100644 index 00000000..e226124d --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/Util/VarUtils.php @@ -0,0 +1,82 @@ + + */ +abstract class VarUtils +{ + /** + * Resolves variable placeholders. + * + * @param string $template A template string + * @param array $vars Variable names + * @param array $values Variable values + * + * @return string The resolved string + * + * @throws \InvalidArgumentException If there is a variable with no value + */ + public static function resolve($template, array $vars, array $values) + { + $map = array(); + foreach ($vars as $var) { + if (false === strpos($template, '{'.$var.'}')) { + continue; + } + + if (!isset($values[$var])) { + throw new \InvalidArgumentException(sprintf('The template "%s" contains the variable "%s", but was not given any value for it.', $template, $var)); + } + + $map['{'.$var.'}'] = $values[$var]; + } + + return strtr($template, $map); + } + + public static function getCombinations(array $vars, array $values) + { + if (!$vars) { + return array(array()); + } + + $combinations = array(); + $nbValues = array(); + foreach ($values as $var => $vals) { + if (!in_array($var, $vars, true)) { + continue; + } + + $nbValues[$var] = count($vals); + } + + for ($i = array_product($nbValues), $c = $i * 2; $i < $c; $i++) { + $k = $i; + $combination = array(); + + foreach ($vars as $var) { + $combination[$var] = $values[$var][$k % $nbValues[$var]]; + $k = intval($k / $nbValues[$var]); + } + + $combinations[] = $combination; + } + + return $combinations; + } + + final private function __construct() { } +} diff --git a/kriswallsmith/assetic/src/Assetic/ValueSupplierInterface.php b/kriswallsmith/assetic/src/Assetic/ValueSupplierInterface.php new file mode 100644 index 00000000..a99874e5 --- /dev/null +++ b/kriswallsmith/assetic/src/Assetic/ValueSupplierInterface.php @@ -0,0 +1,29 @@ + + */ +interface ValueSupplierInterface +{ + /** + * Returns a map of values. + * + * @return array + */ + public function getValues(); +} diff --git a/kriswallsmith/assetic/src/functions.php b/kriswallsmith/assetic/src/functions.php new file mode 100644 index 00000000..d3233b43 --- /dev/null +++ b/kriswallsmith/assetic/src/functions.php @@ -0,0 +1,121 @@ +factory = $factory; +} + +/** + * Returns an array of javascript URLs. + * + * @param array|string $inputs Input strings + * @param array|string $filters Filter names + * @param array $options An array of options + * + * @return array An array of javascript URLs + */ +function assetic_javascripts($inputs = array(), $filters = array(), array $options = array()) +{ + if (!isset($options['output'])) { + $options['output'] = 'js/*.js'; + } + + return _assetic_urls($inputs, $filters, $options); +} + +/** + * Returns an array of stylesheet URLs. + * + * @param array|string $inputs Input strings + * @param array|string $filters Filter names + * @param array $options An array of options + * + * @return array An array of stylesheet URLs + */ +function assetic_stylesheets($inputs = array(), $filters = array(), array $options = array()) +{ + if (!isset($options['output'])) { + $options['output'] = 'css/*.css'; + } + + return _assetic_urls($inputs, $filters, $options); +} + +/** + * Returns an image URL. + * + * @param string $input An input + * @param array|string $filters Filter names + * @param array $options An array of options + * + * @return string An image URL + */ +function assetic_image($input, $filters = array(), array $options = array()) +{ + if (!isset($options['output'])) { + $options['output'] = 'images/*'; + } + + $urls = _assetic_urls($input, $filters, $options); + + return current($urls); +} + +/** + * Returns an array of asset urls. + * + * @param array|string $inputs Input strings + * @param array|string $filters Filter names + * @param array $options An array of options + * + * @return array An array of URLs + */ +function _assetic_urls($inputs = array(), $filters = array(), array $options = array()) +{ + global $_assetic; + + if (!is_array($inputs)) { + $inputs = array_filter(array_map('trim', explode(',', $inputs))); + } + + if (!is_array($filters)) { + $filters = array_filter(array_map('trim', explode(',', $filters))); + } + + $coll = $_assetic->factory->createAsset($inputs, $filters, $options); + + $debug = isset($options['debug']) ? $options['debug'] : $_assetic->factory->isDebug(); + $combine = isset($options['combine']) ? $options['combine'] : !$debug; + + $one = $coll->getTargetPath(); + if ($combine) { + $many = array($one); + } else { + $many = array(); + foreach ($coll as $leaf) { + $many[] = $leaf->getTargetPath(); + } + } + + return new TraversableString($one, $many); +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Asset/AssetCacheTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Asset/AssetCacheTest.php new file mode 100644 index 00000000..00cbfb2c --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Asset/AssetCacheTest.php @@ -0,0 +1,175 @@ +inner = $this->getMock('Assetic\\Asset\\AssetInterface'); + $this->cache = $this->getMock('Assetic\\Cache\\CacheInterface'); + + $this->asset = new AssetCache($this->inner, $this->cache); + } + + public function testLoadFromCache() + { + $content = 'asdf'; + $filter = $this->getMock('Assetic\\Filter\\FilterInterface'); + + $this->inner->expects($this->once()) + ->method('getFilters') + ->will($this->returnValue(array($filter))); + $this->cache->expects($this->once()) + ->method('has') + ->with($this->isType('string')) + ->will($this->returnValue(true)); + $this->cache->expects($this->once()) + ->method('get') + ->with($this->isType('string')) + ->will($this->returnValue($content)); + $this->inner->expects($this->once()) + ->method('setContent') + ->with($content); + + $this->asset->load($filter); + } + + public function testLoadToCache() + { + $content = 'asdf'; + + $this->inner->expects($this->once()) + ->method('getFilters') + ->will($this->returnValue(array())); + $this->cache->expects($this->once()) + ->method('has') + ->with($this->isType('string')) + ->will($this->returnValue(false)); + $this->inner->expects($this->once())->method('load'); + $this->inner->expects($this->once()) + ->method('getContent') + ->will($this->returnValue($content)); + $this->cache->expects($this->once()) + ->method('set') + ->with($this->isType('string'), $content); + + $this->asset->load(); + } + + public function testDumpFromCache() + { + $content = 'asdf'; + + $this->inner->expects($this->once()) + ->method('getFilters') + ->will($this->returnValue(array())); + $this->cache->expects($this->once()) + ->method('has') + ->with($this->isType('string')) + ->will($this->returnValue(true)); + $this->cache->expects($this->once()) + ->method('get') + ->with($this->isType('string')) + ->will($this->returnValue($content)); + + $this->assertEquals($content, $this->asset->dump(), '->dump() returns the cached value'); + } + + public function testDumpToCache() + { + $content = 'asdf'; + + $this->inner->expects($this->once()) + ->method('getFilters') + ->will($this->returnValue(array())); + $this->cache->expects($this->once()) + ->method('has') + ->with($this->isType('string')) + ->will($this->returnValue(false)); + $this->inner->expects($this->once()) + ->method('dump') + ->will($this->returnValue($content)); + $this->cache->expects($this->once()) + ->method('set') + ->with($this->isType('string'), $content); + + $this->assertEquals($content, $this->asset->dump(), '->dump() returns the dumped value'); + } + + public function testEnsureFilter() + { + $filter = $this->getMock('Assetic\\Filter\\FilterInterface'); + $this->inner->expects($this->once())->method('ensureFilter'); + $this->asset->ensureFilter($filter); + } + + public function testGetFilters() + { + $this->inner->expects($this->once()) + ->method('getFilters') + ->will($this->returnValue(array())); + + $this->assertInternalType('array', $this->asset->getFilters(), '->getFilters() returns the inner asset filters'); + } + + public function testGetContent() + { + $this->inner->expects($this->once()) + ->method('getContent') + ->will($this->returnValue('asdf')); + + $this->assertEquals('asdf', $this->asset->getContent(), '->getContent() returns the inner asset content'); + } + + public function testSetContent() + { + $this->inner->expects($this->once()) + ->method('setContent') + ->with('asdf'); + + $this->asset->setContent('asdf'); + } + + public function testGetSourceRoot() + { + $this->inner->expects($this->once()) + ->method('getSourceRoot') + ->will($this->returnValue('asdf')); + + $this->assertEquals('asdf', $this->asset->getSourceRoot(), '->getSourceRoot() returns the inner asset source root'); + } + + public function testGetSourcePath() + { + $this->inner->expects($this->once()) + ->method('getSourcePath') + ->will($this->returnValue('asdf')); + + $this->assertEquals('asdf', $this->asset->getSourcePath(), '->getSourcePath() returns the inner asset source path'); + } + + public function testGetLastModified() + { + $this->inner->expects($this->once()) + ->method('getLastModified') + ->will($this->returnValue(123)); + + $this->assertEquals(123, $this->asset->getLastModified(), '->getLastModified() returns the inner asset last modified'); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Asset/AssetCollectionTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Asset/AssetCollectionTest.php new file mode 100644 index 00000000..ebe646fa --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Asset/AssetCollectionTest.php @@ -0,0 +1,393 @@ +assertInstanceOf('Assetic\\Asset\\AssetInterface', $coll, 'AssetCollection implements AssetInterface'); + } + + public function testLoadFilter() + { + $filter = $this->getMock('Assetic\\Filter\\FilterInterface'); + $filter->expects($this->once())->method('filterLoad'); + + $coll = new AssetCollection(array(new StringAsset('')), array($filter)); + $coll->load(); + } + + public function testDumpFilter() + { + $filter = $this->getMock('Assetic\\Filter\\FilterInterface'); + $filter->expects($this->once())->method('filterDump'); + + $coll = new AssetCollection(array(new StringAsset('')), array($filter)); + $coll->dump(); + } + + public function testNestedCollectionLoad() + { + $content = 'foobar'; + + $count = 0; + $matches = array(); + $filter = new CallablesFilter(function($asset) use ($content, &$matches, &$count) { + ++$count; + if ($content == $asset->getContent()) { + $matches[] = $asset; + } + }); + + $innerColl = new AssetCollection(array(new StringAsset($content))); + $outerColl = new AssetCollection(array($innerColl), array($filter)); + $outerColl->load(); + + $this->assertEquals(1, count($matches), '->load() applies filters to leaves'); + $this->assertEquals(1, $count, '->load() applies filters to leaves only'); + } + + public function testMixedIteration() + { + $asset = new StringAsset('asset'); + $nestedAsset = new StringAsset('nested'); + $innerColl = new AssetCollection(array($nestedAsset)); + + $contents = array(); + $filter = new CallablesFilter(function($asset) use (&$contents) { + $contents[] = $asset->getContent(); + }); + + $coll = new AssetCollection(array($asset, $innerColl), array($filter)); + $coll->load(); + + $this->assertEquals(array('asset', 'nested'), $contents, '->load() iterates over multiple levels'); + } + + public function testLoadDedupBySourceUrl() + { + $asset1 = new StringAsset('asset', array(), '/some/dir', 'foo.bar'); + $asset2 = new StringAsset('asset', array(), '/some/dir', 'foo.bar'); + + $coll = new AssetCollection(array($asset1, $asset2)); + $coll->load(); + + $this->assertEquals('asset', $coll->getContent(), '->load() detects duplicate assets based on source URL'); + } + + public function testLoadDedupByStrictEquality() + { + $asset = new StringAsset('foo'); + + $coll = new AssetCollection(array($asset, $asset)); + $coll->load(); + + $this->assertEquals('foo', $coll->getContent(), '->load() detects duplicate assets based on strict equality'); + } + + public function testDumpDedupBySourceUrl() + { + $asset1 = new StringAsset('asset', array(), '/some/dir', 'foo.bar'); + $asset2 = new StringAsset('asset', array(), '/some/dir', 'foo.bar'); + + $coll = new AssetCollection(array($asset1, $asset2)); + $coll->load(); + + $this->assertEquals('asset', $coll->dump(), '->dump() detects duplicate assets based on source URL'); + } + + public function testDumpDedupByStrictEquality() + { + $asset = new StringAsset('foo'); + + $coll = new AssetCollection(array($asset, $asset)); + $coll->load(); + + $this->assertEquals('foo', $coll->dump(), '->dump() detects duplicate assets based on strict equality'); + } + + public function testIterationFilters() + { + $count = 0; + $filter = new CallablesFilter(function() use (&$count) { ++$count; }); + + $coll = new AssetCollection(); + $coll->add(new StringAsset('')); + $coll->ensureFilter($filter); + + foreach ($coll as $asset) { + $asset->dump(); + } + + $this->assertEquals(1, $count, 'collection filters are called when child assets are iterated over'); + } + + public function testSetContent() + { + $coll = new AssetCollection(); + $coll->setContent('asdf'); + + $this->assertEquals('asdf', $coll->getContent(), '->setContent() sets the content'); + } + + /** + * @dataProvider getTimestampsAndExpected + */ + public function testGetLastModified($timestamps, $expected) + { + $assets = array(); + + for ($i = 0; $i < count($timestamps); $i++) { + $asset = $this->getMock('Assetic\\Asset\\AssetInterface'); + $asset->expects($this->once()) + ->method('getLastModified') + ->will($this->returnValue($timestamps[$i])); + $assets[$i] = $asset; + } + + $coll = new AssetCollection($assets); + + $this->assertEquals($expected, $coll->getLastModified(), '->getLastModifed() returns the highest last modified'); + } + + public function testGetLastModifiedWithValues() + { + $vars = array('locale'); + $asset = new FileAsset(__DIR__.'/../Fixture/messages.{locale}.js', array(), null, null, $vars); + + $coll = new AssetCollection(array($asset), array(), null, $vars); + $coll->setValues(array('locale' => 'en')); + try { + $coll->getLastModified(); + } catch (\InvalidArgumentException $e) { + $this->fail("->getLastModified() shouldn't fail for assets with vars"); + } + } + + public function getTimestampsAndExpected() + { + return array( + array(array(1, 2, 3), 3), + array(array(5, 4, 3), 5), + array(array(3, 8, 5), 8), + array(array(3, 8, null), 8), + ); + } + + public function testRecursiveIteration() + { + $asset1 = $this->getMock('Assetic\\Asset\\AssetInterface'); + $asset2 = $this->getMock('Assetic\\Asset\\AssetInterface'); + $asset3 = $this->getMock('Assetic\\Asset\\AssetInterface'); + $asset4 = $this->getMock('Assetic\\Asset\\AssetInterface'); + + $coll3 = new AssetCollection(array($asset1, $asset2)); + $coll2 = new AssetCollection(array($asset3, $coll3)); + $coll1 = new AssetCollection(array($asset4, $coll2)); + + $i = 0; + foreach ($coll1 as $a) { + $i++; + } + + $this->assertEquals(4, $i, 'iteration with a recursive iterator is recursive'); + } + + public function testRecursiveDeduplication() + { + $asset = $this->getMock('Assetic\\Asset\\AssetInterface'); + + $coll3 = new AssetCollection(array($asset, $asset)); + $coll2 = new AssetCollection(array($asset, $coll3)); + $coll1 = new AssetCollection(array($asset, $coll2)); + + $i = 0; + foreach ($coll1 as $a) { + $i++; + } + + $this->assertEquals(1, $i, 'deduplication is performed recursively'); + } + + public function testIteration() + { + $asset1 = new StringAsset('asset1', array(), '/some/dir', 'foo.css'); + $asset2 = new StringAsset('asset2', array(), '/some/dir', 'foo.css'); + $asset3 = new StringAsset('asset3', array(), '/some/dir', 'bar.css'); + + $coll = new AssetCollection(array($asset1, $asset2, $asset3)); + + $count = 0; + foreach ($coll as $a) { + ++$count; + } + + $this->assertEquals(2, $count, 'iterator filters duplicates based on url'); + } + + public function testBasenameCollision() + { + $asset1 = new StringAsset('asset1', array(), '/some/dir', 'foo/foo.css'); + $asset2 = new StringAsset('asset2', array(), '/some/dir', 'bar/foo.css'); + + $coll = new AssetCollection(array($asset1, $asset2)); + + $urls = array(); + foreach ($coll as $leaf) { + $urls[] = $leaf->getTargetPath(); + } + + $this->assertEquals(2, count(array_unique($urls)), 'iterator prevents basename collisions'); + } + + public function testTargetNameGeneration() + { + $path = '/testing/dir.ectory/path/file.ext'; + + $coll = new AssetCollection(array(new StringAsset('asset1'), new StringAsset('asset2'))); + $coll->setTargetPath($path); + + foreach ($coll as $asset) { + $this->assertEquals(dirname($path), dirname($asset->getTargetPath())); + } + } + + public function testEmptyMtime() + { + $coll = new AssetCollection(); + $this->assertNull($coll->getLastModified(), '->getLastModified() returns null on empty collection'); + } + + public function testLeafManipulation() + { + $coll = new AssetCollection(array(new StringAsset('asdf'))); + + foreach ($coll as $leaf) { + $leaf->setTargetPath('asdf'); + } + + foreach ($coll as $leaf) { + $this->assertEquals('asdf', $leaf->getTargetPath(), 'leaf changes persist between iterations'); + } + } + + public function testRemoveLeaf() + { + $coll = new AssetCollection(array( + $leaf = new StringAsset('asdf'), + )); + + $this->assertTrue($coll->removeLeaf($leaf)); + } + + public function testRemoveRecursiveLeaf() + { + $coll = new AssetCollection(array( + new AssetCollection(array( + $leaf = new StringAsset('asdf'), + )) + )); + + $this->assertTrue($coll->removeLeaf($leaf)); + } + + public function testRemoveInvalidLeaf() + { + $this->setExpectedException('InvalidArgumentException'); + + $coll = new AssetCollection(); + $coll->removeLeaf(new StringAsset('asdf')); + } + + public function testReplaceLeaf() + { + $coll = new AssetCollection(array( + $leaf = new StringAsset('asdf'), + )); + + $this->assertTrue($coll->replaceLeaf($leaf, new StringAsset('foo'))); + } + + public function testReplaceRecursiveLeaf() + { + $coll = new AssetCollection(array( + new AssetCollection(array( + $leaf = new StringAsset('asdf'), + )), + )); + + $this->assertTrue($coll->replaceLeaf($leaf, new StringAsset('foo'))); + } + + public function testReplaceInvalidLeaf() + { + $this->setExpectedException('InvalidArgumentException'); + + $coll = new AssetCollection(); + $coll->replaceLeaf(new StringAsset('foo'), new StringAsset('bar')); + } + + public function testClone() + { + $coll1 = new AssetCollection(); + $coll1->ensureFilter(new CallablesFilter()); + + $coll2 = clone $coll1; + $coll2->ensureFilter(new CallablesFilter()); + + $this->assertCount(1, $coll1->getFilters()); + $this->assertCount(2, $coll2->getFilters()); + } + + public function testNewFilterOnCloneDoesNotPropagateToPrototype() + { + $coll1 = new AssetCollection(); + $coll1->add(new StringAsset("")); + + $coll2 = clone $coll1; + $coll2->ensureFilter(new CallablesFilter()); + + // Internally, this will set up the "clones" for collection #2 with one filter + foreach ($coll2 as $asset) { } + + // The clones on collection #1 must not be affected + foreach ($coll1 as $asset) { + $this->assertCount(0, $asset->getFilters()); + } + } + + public function testClearingFiltersWorksAfterCollectionHasBeenIterated() + { + $coll = new AssetCollection(); + $coll->add(new StringAsset("")); + $coll->ensureFilter(new CallablesFilter()); + + /* This iteration is necessary to internally prime the collection's + "clones" with one filter. */ + foreach ($coll as $asset) { + $this->assertCount(1, $asset->getFilters()); + } + + $coll->clearFilters(); + + foreach ($coll as $asset) { + $this->assertCount(0, $asset->getFilters()); + } + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Asset/AssetReferenceTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Asset/AssetReferenceTest.php new file mode 100644 index 00000000..bc7522cd --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Asset/AssetReferenceTest.php @@ -0,0 +1,126 @@ +am = $this->getMock('Assetic\\AssetManager'); + $this->ref = new AssetReference($this->am, 'foo'); + } + + /** + * @dataProvider getMethodAndRetVal + */ + public function testMethods($method, $returnValue) + { + $asset = $this->getMock('Assetic\\Asset\\AssetInterface'); + + $this->am->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue($asset)); + $asset->expects($this->once()) + ->method($method) + ->will($this->returnValue($returnValue)); + + $this->assertEquals($returnValue, $this->ref->$method(), '->'.$method.'() returns the asset value'); + } + + public function getMethodAndRetVal() + { + return array( + array('getContent', 'asdf'), + array('getSourceRoot', 'asdf'), + array('getSourcePath', 'asdf'), + array('getTargetPath', 'asdf'), + array('getLastModified', 123), + ); + } + + public function testLazyFilters() + { + $this->am->expects($this->never())->method('get'); + $this->ref->ensureFilter($this->getMock('Assetic\\Filter\\FilterInterface')); + } + + public function testFilterFlush() + { + $asset = $this->getMock('Assetic\\Asset\\AssetInterface'); + + $this->am->expects($this->exactly(2)) + ->method('get') + ->with('foo') + ->will($this->returnValue($asset)); + $asset->expects($this->once())->method('ensureFilter'); + $asset->expects($this->once()) + ->method('getFilters') + ->will($this->returnValue(array())); + + $this->ref->ensureFilter($this->getMock('Assetic\\Filter\\FilterInterface')); + + $this->assertInternalType('array', $this->ref->getFilters(), '->getFilters() flushes and returns filters'); + } + + public function testSetContent() + { + $asset = $this->getMock('Assetic\\Asset\\AssetInterface'); + + $this->am->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue($asset)); + $asset->expects($this->once()) + ->method('setContent') + ->with('asdf'); + + $this->ref->setContent('asdf'); + } + + public function testLoad() + { + $filter = $this->getMock('Assetic\\Filter\\FilterInterface'); + $asset = $this->getMock('Assetic\\Asset\\AssetInterface'); + + $this->am->expects($this->exactly(2)) + ->method('get') + ->with('foo') + ->will($this->returnValue($asset)); + $asset->expects($this->once()) + ->method('load') + ->with($filter); + + $this->ref->load($filter); + } + + public function testDump() + { + $filter = $this->getMock('Assetic\\Filter\\FilterInterface'); + $asset = $this->getMock('Assetic\\Asset\\AssetInterface'); + + $this->am->expects($this->exactly(2)) + ->method('get') + ->with('foo') + ->will($this->returnValue($asset)); + $asset->expects($this->once()) + ->method('dump') + ->with($filter); + + $this->ref->dump($filter); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Asset/FileAssetTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Asset/FileAssetTest.php new file mode 100644 index 00000000..a005d14d --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Asset/FileAssetTest.php @@ -0,0 +1,74 @@ +assertInstanceOf('Assetic\\Asset\\AssetInterface', $asset, 'Asset implements AssetInterface'); + } + + public function testLazyLoading() + { + $asset = new FileAsset(__FILE__); + $this->assertEmpty($asset->getContent(), 'The asset content is empty before load'); + + $asset->load(); + $this->assertNotEmpty($asset->getContent(), 'The asset content is not empty after load'); + } + + public function testGetLastModifiedType() + { + $asset = new FileAsset(__FILE__); + $this->assertInternalType('integer', $asset->getLastModified(), '->getLastModified() returns an integer'); + } + + public function testGetLastModifiedTypeFileNotFound() + { + $asset = new FileAsset(__DIR__ . "/foo/bar/baz.css"); + + $this->setExpectedException("RuntimeException", "The source file"); + $asset->getLastModified(); + } + + public function testGetLastModifiedValue() + { + $asset = new FileAsset(__FILE__); + $this->assertLessThan(time(), $asset->getLastModified(), '->getLastModified() returns the mtime'); + } + + public function testDefaultBaseAndPath() + { + $asset = new FileAsset(__FILE__); + $this->assertEquals(__DIR__, $asset->getSourceRoot(), '->__construct() defaults base to the asset directory'); + $this->assertEquals(basename(__FILE__), $asset->getSourcePath(), '->__construct() defaults path to the asset basename'); + $this->assertEquals(__DIR__, $asset->getSourceDirectory(), '->__construct() derives the asset directory'); + } + + public function testPathGuessing() + { + $asset = new FileAsset(__FILE__, array(), __DIR__); + $this->assertEquals(basename(__FILE__), $asset->getSourcePath(), '->__construct() guesses the asset path'); + $this->assertEquals(__DIR__, $asset->getSourceDirectory(), '->__construct() derives the asset directory'); + } + + public function testInvalidBase() + { + $this->setExpectedException('InvalidArgumentException'); + + $asset = new FileAsset(__FILE__, array(), __DIR__.'/foo'); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Asset/GlobAssetTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Asset/GlobAssetTest.php new file mode 100644 index 00000000..f4bdcd9f --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Asset/GlobAssetTest.php @@ -0,0 +1,61 @@ +assertInstanceOf('Assetic\\Asset\\AssetInterface', $asset, 'Asset implements AssetInterface'); + } + + public function testIteration() + { + $assets = new GlobAsset(__DIR__.'/*.php'); + $this->assertGreaterThan(0, iterator_count($assets), 'GlobAsset initializes for iteration'); + } + + public function testRecursiveIteration() + { + $assets = new GlobAsset(__DIR__.'/*.php'); + $this->assertGreaterThan(0, iterator_count($assets), 'GlobAsset initializes for recursive iteration'); + } + + public function testGetLastModifiedType() + { + $assets = new GlobAsset(__DIR__.'/*.php'); + $this->assertInternalType('integer', $assets->getLastModified(), '->getLastModified() returns an integer'); + } + + public function testGetLastModifiedValue() + { + $assets = new GlobAsset(__DIR__.'/*.php'); + $this->assertLessThan(time(), $assets->getLastModified(), '->getLastModified() returns a file mtime'); + } + + public function testLoad() + { + $assets = new GlobAsset(__DIR__.'/*.php'); + $assets->load(); + + $this->assertNotEmpty($assets->getContent(), '->load() loads contents'); + } + + public function testDump() + { + $assets = new GlobAsset(__DIR__.'/*.php'); + $this->assertNotEmpty($assets->dump(), '->dump() dumps contents'); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Asset/HttpAssetTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Asset/HttpAssetTest.php new file mode 100644 index 00000000..d36d2d35 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Asset/HttpAssetTest.php @@ -0,0 +1,61 @@ +assertInternalType('integer', $asset->getLastModified(), '->getLastModified() returns an integer'); + } + + /** + * @group http + */ + public function testProtocolRelativeUrl() + { + $asset = new HttpAsset(substr(self::JQUERY, 5)); + $asset->load(); + $this->assertNotEmpty($asset->getContent()); + } + + public function testMalformedUrl() + { + $this->setExpectedException('InvalidArgumentException'); + + new HttpAsset(__FILE__); + } + + public function testInvalidUrl() + { + $this->setExpectedException('RuntimeException'); + + $asset = new HttpAsset('http://invalid.com/foobar'); + $asset->load(); + } + + public function testSourceMetadata() + { + $asset = new HttpAsset(self::JQUERY); + $this->assertEquals('http://ajax.googleapis.com', $asset->getSourceRoot(), '->__construct() set the source root'); + $this->assertEquals('ajax/libs/jquery/1.6.1/jquery.min.js', $asset->getSourcePath(), '->__construct() set the source path'); + $this->assertEquals('http://ajax.googleapis.com/ajax/libs/jquery/1.6.1', $asset->getSourceDirectory(), '->__construct() sets the source directory'); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Asset/StringAssetTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Asset/StringAssetTest.php new file mode 100644 index 00000000..8184a9a7 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Asset/StringAssetTest.php @@ -0,0 +1,90 @@ +assertInstanceOf('Assetic\\Asset\\AssetInterface', $asset, 'Asset implements AssetInterface'); + } + + public function testLoadAppliesFilters() + { + $filter = $this->getMock('Assetic\\Filter\\FilterInterface'); + $filter->expects($this->once())->method('filterLoad'); + + $asset = new StringAsset('foo', array($filter)); + $asset->load(); + } + + public function testAutomaticLoad() + { + $filter = $this->getMock('Assetic\\Filter\\FilterInterface'); + $filter->expects($this->once())->method('filterLoad'); + + $asset = new StringAsset('foo', array($filter)); + $asset->dump(); + } + + public function testGetFilters() + { + $asset = new StringAsset(''); + $this->assertInternalType('array', $asset->getFilters(), '->getFilters() returns an array'); + } + + public function testLoadAppliesAdditionalFilter() + { + $asset = new StringAsset(''); + $asset->load(); + + $filter = $this->getMock('Assetic\\Filter\\FilterInterface'); + $filter->expects($this->once()) + ->method('filterLoad') + ->with($asset); + + $asset->load($filter); + } + + public function testDumpAppliesAdditionalFilter() + { + $asset = new StringAsset(''); + + $filter = $this->getMock('Assetic\\Filter\\FilterInterface'); + $filter->expects($this->once()) + ->method('filterDump') + ->with($asset); + + $asset->dump($filter); + } + + public function testLastModified() + { + $asset = new StringAsset(''); + $asset->setLastModified(123); + $this->assertEquals(123, $asset->getLastModified(), '->getLastModified() return the set last modified value'); + } + + public function testGetContentNullUnlessLoaded() + { + // see https://github.com/kriswallsmith/assetic/pull/432 + $asset = new StringAsset("test"); + $this->assertNull($asset->getContent(), '->getContent() returns null unless load() has been called.'); + + $asset->load(); + + $this->assertEquals("test", $asset->getContent(), '->getContent() returns the content after load()'); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/AssetManagerTest.php b/kriswallsmith/assetic/tests/Assetic/Test/AssetManagerTest.php new file mode 100644 index 00000000..631e3402 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/AssetManagerTest.php @@ -0,0 +1,61 @@ +am = new AssetManager(); + } + + public function testGetAsset() + { + $asset = $this->getMock('Assetic\\Asset\\AssetInterface'); + $this->am->set('foo', $asset); + $this->assertSame($asset, $this->am->get('foo'), '->get() returns an asset'); + } + + public function testGetInvalidAsset() + { + $this->setExpectedException('InvalidArgumentException'); + $this->am->get('foo'); + } + + public function testHas() + { + $asset = $this->getMock('Assetic\\Asset\\AssetInterface'); + $this->am->set('foo', $asset); + + $this->assertTrue($this->am->has('foo'), '->has() returns true if the asset is set'); + $this->assertFalse($this->am->has('bar'), '->has() returns false if the asset is not set'); + } + + public function testInvalidName() + { + $this->setExpectedException('InvalidArgumentException'); + + $this->am->set('@foo', $this->getMock('Assetic\\Asset\\AssetInterface')); + } + + public function testClear() + { + $this->am->set('foo', $this->getMock('Assetic\Asset\AssetInterface')); + $this->am->clear(); + + $this->assertFalse($this->am->has('foo')); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/AssetWriterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/AssetWriterTest.php new file mode 100644 index 00000000..6fa1ef3b --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/AssetWriterTest.php @@ -0,0 +1,132 @@ +dir = sys_get_temp_dir().'/assetic_tests_'.rand(11111, 99999); + mkdir($this->dir); + $this->writer = new AssetWriter($this->dir, array( + 'locale' => array('en', 'de', 'fr'), + 'browser' => array('ie', 'firefox', 'other'), + 'gzip' => array('gzip', '') + )); + } + + protected function tearDown() + { + array_map('unlink', glob($this->dir.'/*')); + rmdir($this->dir); + } + + public function testWriteManagerAssets() + { + $asset = $this->getMock('Assetic\\Asset\\AssetInterface'); + $am = $this->getMock('Assetic\\AssetManager'); + + $am->expects($this->once()) + ->method('getNames') + ->will($this->returnValue(array('foo'))); + $am->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue($asset)); + $asset->expects($this->atLeastOnce()) + ->method('getTargetPath') + ->will($this->returnValue('target_url')); + $asset->expects($this->once()) + ->method('dump') + ->will($this->returnValue('content')); + $asset->expects($this->atLeastOnce()) + ->method('getVars') + ->will($this->returnValue(array())); + $asset->expects($this->atLeastOnce()) + ->method('getValues') + ->will($this->returnValue(array())); + + $this->writer->writeManagerAssets($am); + + $this->assertFileExists($this->dir.'/target_url'); + $this->assertEquals('content', file_get_contents($this->dir.'/target_url')); + } + + public function testWriteAssetWithVars() + { + $asset = $this->getMock('Assetic\Asset\AssetInterface'); + $asset->expects($this->atLeastOnce()) + ->method('getVars') + ->will($this->returnValue(array('locale'))); + + $self = $this; + $expectedValues = array( + array('locale' => 'en'), + array('locale' => 'de'), + array('locale' => 'fr'), + ); + $asset->expects($this->exactly(3)) + ->method('setValues') + ->will($this->returnCallback(function($values) use ($self, $expectedValues) { + static $counter = 0; + $self->assertEquals($expectedValues[$counter++], $values); + })); + $asset->expects($this->exactly(3)) + ->method('getValues') + ->will($this->returnCallback(function() use ($expectedValues) { + static $counter = 0; + + return $expectedValues[$counter++]; + })); + + $asset->expects($this->exactly(3)) + ->method('dump') + ->will($this->onConsecutiveCalls('en', 'de', 'fr')); + + $asset->expects($this->atLeastOnce()) + ->method('getTargetPath') + ->will($this->returnValue('target.{locale}')); + + $this->writer->writeAsset($asset); + + $this->assertFileExists($this->dir.'/target.en'); + $this->assertFileExists($this->dir.'/target.de'); + $this->assertFileExists($this->dir.'/target.fr'); + $this->assertEquals('en', file_get_contents($this->dir.'/target.en')); + $this->assertEquals('de', file_get_contents($this->dir.'/target.de')); + $this->assertEquals('fr', file_get_contents($this->dir.'/target.fr')); + } + + public function testAssetWithInputVars() + { + $asset = new FileAsset(__DIR__.'/Fixture/messages.{locale}.js', + array(), null, null, array('locale')); + $asset->setTargetPath('messages.{locale}.js'); + + $this->writer->writeAsset($asset); + + $this->assertFileExists($this->dir.'/messages.en.js'); + $this->assertFileExists($this->dir.'/messages.de.js'); + $this->assertFileExists($this->dir.'/messages.fr.js'); + $this->assertEquals('var messages = {"text.greeting": "Hello %name%!"};', + file_get_contents($this->dir.'/messages.en.js')); + $this->assertEquals('var messages = {"text.greeting": "Hallo %name%!"};', + file_get_contents($this->dir.'/messages.de.js')); + $this->assertEquals('var messages = {"text.greet": "All\u00f4 %name%!"};', + file_get_contents($this->dir.'/messages.fr.js')); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Cache/ApcCacheTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Cache/ApcCacheTest.php new file mode 100644 index 00000000..5e538f1a --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Cache/ApcCacheTest.php @@ -0,0 +1,42 @@ +markTestSkipped('APC must be installed and the apc.enable_cli setting must be enabled.'); + } + } + + public function testCache() + { + $cache = new ApcCache(); + + $this->assertFalse($cache->has('foo')); + + $cache->set('foo', 'bar'); + $this->assertEquals('bar', $cache->get('foo')); + + $this->assertTrue($cache->has('foo')); + + $cache->remove('foo'); + $this->assertFalse($cache->has('foo')); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Cache/ArrayCacheTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Cache/ArrayCacheTest.php new file mode 100644 index 00000000..0acfbcdf --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Cache/ArrayCacheTest.php @@ -0,0 +1,35 @@ +assertFalse($cache->has('foo')); + + $cache->set('foo', 'bar'); + $this->assertEquals('bar', $cache->get('foo')); + + $this->assertTrue($cache->has('foo')); + + $cache->remove('foo'); + $this->assertFalse($cache->has('foo')); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Cache/ConfigCacheTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Cache/ConfigCacheTest.php new file mode 100644 index 00000000..ff1fbf0f --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Cache/ConfigCacheTest.php @@ -0,0 +1,65 @@ +dir = sys_get_temp_dir().'/assetic/tests/config_cache'; + $this->cache = new ConfigCache($this->dir); + } + + protected function tearDown() + { + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->dir, \FilesystemIterator::SKIP_DOTS)) as $file) { + unlink($file->getPathname()); + } + } + + public function testCache() + { + $this->cache->set('foo', array(1, 2, 3)); + $this->assertEquals(array(1, 2, 3), $this->cache->get('foo'), '->get() returns the ->set() value'); + } + + public function testTimestamp() + { + $this->cache->set('bar', array(4, 5, 6)); + $this->assertInternalType('integer', $time = $this->cache->getTimestamp('bar'), '->getTimestamp() returns an integer'); + $this->assertNotEmpty($time, '->getTimestamp() returns a non-empty number'); + } + + public function testInvalidValue() + { + $this->setExpectedException('RuntimeException'); + $this->cache->get('_invalid'); + } + + public function testInvalidTimestamp() + { + $this->setExpectedException('RuntimeException'); + $this->cache->getTimestamp('_invalid'); + } + + public function testHas() + { + $this->cache->set('foo', 'bar'); + $this->assertTrue($this->cache->has('foo')); + $this->assertFalse($this->cache->has('_invalid')); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Cache/ExpiringCacheTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Cache/ExpiringCacheTest.php new file mode 100644 index 00000000..5bf195ac --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Cache/ExpiringCacheTest.php @@ -0,0 +1,111 @@ +inner = $this->getMock('Assetic\\Cache\\CacheInterface'); + $this->lifetime = 3600; + $this->cache = new ExpiringCache($this->inner, $this->lifetime); + } + + public function testHasExpired() + { + $key = 'asdf'; + $expiresKey = 'asdf.expires'; + $thePast = 0; + + $this->inner->expects($this->once()) + ->method('has') + ->with($key) + ->will($this->returnValue(true)); + $this->inner->expects($this->once()) + ->method('get') + ->with($expiresKey) + ->will($this->returnValue($thePast)); + $this->inner->expects($this->at(2)) + ->method('remove') + ->with($expiresKey); + $this->inner->expects($this->at(3)) + ->method('remove') + ->with($key); + + $this->assertFalse($this->cache->has($key), '->has() returns false if an expired value exists'); + } + + public function testHasNotExpired() + { + $key = 'asdf'; + $expiresKey = 'asdf.expires'; + $theFuture = time() * 2; + + $this->inner->expects($this->once()) + ->method('has') + ->with($key) + ->will($this->returnValue(true)); + $this->inner->expects($this->once()) + ->method('get') + ->with($expiresKey) + ->will($this->returnValue($theFuture)); + + $this->assertTrue($this->cache->has($key), '->has() returns true if a value the not expired'); + } + + public function testSetLifetime() + { + $key = 'asdf'; + $expiresKey = 'asdf.expires'; + $value = 'qwerty'; + + $this->inner->expects($this->at(0)) + ->method('set') + ->with($expiresKey, $this->greaterThanOrEqual(time() + $this->lifetime)); + $this->inner->expects($this->at(1)) + ->method('set') + ->with($key, $value); + + $this->cache->set($key, $value); + } + + public function testRemove() + { + $key = 'asdf'; + $expiresKey = 'asdf.expires'; + + $this->inner->expects($this->at(0)) + ->method('remove') + ->with($expiresKey); + $this->inner->expects($this->at(1)) + ->method('remove') + ->with($key); + + $this->cache->remove($key); + } + + public function testGet() + { + $this->inner->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue('bar')); + + $this->assertEquals('bar', $this->cache->get('foo'), '->get() returns the cached value'); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Cache/FilesystemCacheTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Cache/FilesystemCacheTest.php new file mode 100644 index 00000000..cd1abe51 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Cache/FilesystemCacheTest.php @@ -0,0 +1,51 @@ +assertFalse($cache->has('foo')); + + $cache->set('foo', 'bar'); + $this->assertEquals('bar', $cache->get('foo')); + + $this->assertTrue($cache->has('foo')); + + $cache->remove('foo'); + $this->assertFalse($cache->has('foo')); + } + + public function testSetCreatesDir() + { + $dir = sys_get_temp_dir().'/assetic/fscachetest'; + + $tearDown = function() use ($dir) { + array_map('unlink', glob($dir.'/*')); + @rmdir($dir); + }; + + $tearDown(); + + $cache = new FilesystemCache($dir); + $cache->set('foo', 'bar'); + + $this->assertFileExists($dir.'/foo'); + + $tearDown(); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/AsseticExtensionTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/AsseticExtensionTest.php new file mode 100644 index 00000000..50e8053b --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/AsseticExtensionTest.php @@ -0,0 +1,249 @@ +markTestSkipped('Twig is not installed.'); + } + + $this->am = $this->getMock('Assetic\\AssetManager'); + $this->fm = $this->getMock('Assetic\\FilterManager'); + + $this->valueSupplier = $this->getMock('Assetic\ValueSupplierInterface'); + + $this->factory = new AssetFactory(__DIR__.'/templates'); + $this->factory->setAssetManager($this->am); + $this->factory->setFilterManager($this->fm); + + $this->twig = new \Twig_Environment(); + $this->twig->setLoader(new \Twig_Loader_Filesystem(__DIR__.'/templates')); + $this->twig->addExtension(new AsseticExtension($this->factory, array(), $this->valueSupplier)); + } + + public function testReference() + { + $asset = $this->getMock('Assetic\\Asset\\AssetInterface'); + $this->am->expects($this->any()) + ->method('get') + ->with('foo') + ->will($this->returnValue($asset)); + + $xml = $this->renderXml('reference.twig'); + $this->assertEquals(1, count($xml->asset)); + $this->assertStringStartsWith('css/', (string) $xml->asset['url']); + } + + public function testGlob() + { + $xml = $this->renderXml('glob.twig'); + $this->assertEquals(1, count($xml->asset)); + $this->assertStringStartsWith('css/', (string) $xml->asset['url']); + } + + public function testAbsolutePath() + { + $xml = $this->renderXml('absolute_path.twig'); + $this->assertEquals(1, count($xml->asset)); + $this->assertStringStartsWith('css/', (string) $xml->asset['url']); + } + + public function testFilters() + { + $filter = $this->getMock('Assetic\\Filter\\FilterInterface'); + + $this->fm->expects($this->at(0)) + ->method('get') + ->with('foo') + ->will($this->returnValue($filter)); + $this->fm->expects($this->at(1)) + ->method('get') + ->with('bar') + ->will($this->returnValue($filter)); + + $xml = $this->renderXml('filters.twig'); + $this->assertEquals(1, count($xml->asset)); + $this->assertStringStartsWith('css/', (string) $xml->asset['url']); + } + + public function testOptionalFilter() + { + $filter = $this->getMock('Assetic\\Filter\\FilterInterface'); + + $this->fm->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue($filter)); + + $xml = $this->renderXml('optional_filter.twig'); + $this->assertEquals(1, count($xml->asset)); + $this->assertStringStartsWith('css/', (string) $xml->asset['url']); + } + + public function testOutputPattern() + { + $xml = $this->renderXml('output_pattern.twig'); + $this->assertEquals(1, count($xml->asset)); + $this->assertStringStartsWith('css/packed/', (string) $xml->asset['url']); + $this->assertStringEndsWith('.css', (string) $xml->asset['url']); + } + + public function testOutput() + { + $xml = $this->renderXml('output_url.twig'); + $this->assertEquals(1, count($xml->asset)); + $this->assertEquals('explicit_url.css', (string) $xml->asset['url']); + } + + public function testMixture() + { + $asset = $this->getMock('Assetic\\Asset\\AssetInterface'); + $this->am->expects($this->any()) + ->method('get') + ->with('foo') + ->will($this->returnValue($asset)); + + $xml = $this->renderXml('mixture.twig'); + $this->assertEquals(1, count($xml->asset)); + $this->assertEquals('packed/mixture', (string) $xml->asset['url']); + } + + public function testDebug() + { + $filter = $this->getMock('Assetic\\Filter\\FilterInterface'); + + $this->fm->expects($this->once()) + ->method('get') + ->with('bar') + ->will($this->returnValue($filter)); + + $xml = $this->renderXml('debug.twig'); + $this->assertEquals(2, count($xml->asset)); + $this->assertStringStartsWith('css/packed_', (string) $xml->asset[0]['url']); + $this->assertStringEndsWith('.css', (string) $xml->asset[0]['url']); + } + + public function testCombine() + { + $filter = $this->getMock('Assetic\\Filter\\FilterInterface'); + + $this->fm->expects($this->once()) + ->method('get') + ->with('bar') + ->will($this->returnValue($filter)); + + $xml = $this->renderXml('combine.twig'); + $this->assertEquals(1, count($xml->asset)); + $this->assertEquals('css/packed.css', (string) $xml->asset[0]['url']); + } + + public function testImage() + { + $xml = $this->renderXml('image.twig'); + $this->assertEquals(1, count($xml->image)); + $this->assertStringEndsWith('.png', (string) $xml->image[0]['url']); + } + + public function testFilterFunction() + { + $filter = $this->getMock('Assetic\\Filter\\FilterInterface'); + + $this->fm->expects($this->once()) + ->method('get') + ->with('some_filter') + ->will($this->returnValue($filter)); + + $this->twig->addExtension(new AsseticExtension($this->factory, array( + 'some_func' => array( + 'filter' => 'some_filter', + 'options' => array('output' => 'css/*.css'), + ), + ))); + + $xml = $this->renderXml('function.twig'); + $this->assertEquals(1, count($xml->asset)); + $this->assertStringEndsWith('.css', (string) $xml->asset[0]['url']); + } + + public function testVariables() + { + $this->valueSupplier->expects($this->once()) + ->method('getValues') + ->will($this->returnValue(array('foo' => 'a', 'bar' => 'b'))); + + $xml = $this->renderXml('variables.twig'); + $this->assertEquals(2, $xml->url->count()); + $this->assertEquals("js/7d0828c.a.b_foo_1.js", (string) $xml->url[0]); + $this->assertEquals("js/7d0828c.a.b_variable_input._2.js", (string) $xml->url[1]); + } + + public function testMultipleSameVariableValues() + { + $vars = array('locale'); + $asset = new FileAsset(__DIR__.'/../Fixture/messages.{locale}.js', array(), null, null, $vars); + + $coll = new AssetCollection(array($asset), array(), null, $vars); + + $coll->setTargetPath('output.{locale}.js'); + + $coll->setValues(array('locale' => 'en')); + foreach($coll as $asset) { + $this->assertEquals('output.{locale}_messages._1.js', $asset->getTargetPath(), 'targetPath must not contain several time the same variable'); + } + } + + /** + * @expectedException \Twig_Error_Syntax + */ + public function testUnclosedTag() + { + $this->renderXml('unclosed_tag.twig'); + } + + private function renderXml($name, $context = array()) + { + return new \SimpleXMLElement($this->twig->loadTemplate($name)->render($context)); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/TwigFormulaLoaderTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/TwigFormulaLoaderTest.php new file mode 100644 index 00000000..c3cbea76 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/TwigFormulaLoaderTest.php @@ -0,0 +1,108 @@ +markTestSkipped('Twig is not installed.'); + } + + $this->am = $this->getMock('Assetic\\AssetManager'); + $this->fm = $this->getMock('Assetic\\FilterManager'); + + $factory = new AssetFactory(__DIR__.'/templates'); + $factory->setAssetManager($this->am); + $factory->setFilterManager($this->fm); + + $twig = new \Twig_Environment(); + $twig->addExtension(new AsseticExtension($factory, array( + 'some_func' => array( + 'filter' => 'some_filter', + 'options' => array('output' => 'css/*.css'), + ), + ))); + + $this->loader = new TwigFormulaLoader($twig); + } + + public function testMixture() + { + $asset = $this->getMock('Assetic\\Asset\\AssetInterface'); + + $expected = array( + 'mixture' => array( + array('foo', 'foo/*', '@foo'), + array(), + array( + 'output' => 'packed/mixture', + 'name' => 'mixture', + 'debug' => false, + 'combine' => null, + 'vars' => array(), + ), + ), + ); + + $resource = $this->getMock('Assetic\\Factory\\Resource\\ResourceInterface'); + $resource->expects($this->once()) + ->method('getContent') + ->will($this->returnValue(file_get_contents(__DIR__.'/templates/mixture.twig'))); + $this->am->expects($this->any()) + ->method('get') + ->with('foo') + ->will($this->returnValue($asset)); + + $formulae = $this->loader->load($resource); + $this->assertEquals($expected, $formulae); + } + + public function testFunction() + { + $expected = array( + 'my_asset' => array( + array('path/to/asset'), + array('some_filter'), + array('output' => 'css/*.css', 'name' => 'my_asset'), + ), + ); + + $resource = $this->getMock('Assetic\\Factory\\Resource\\ResourceInterface'); + $resource->expects($this->once()) + ->method('getContent') + ->will($this->returnValue(file_get_contents(__DIR__.'/templates/function.twig'))); + + $formulae = $this->loader->load($resource); + $this->assertEquals($expected, $formulae); + } + + public function testUnclosedTag() + { + $resource = $this->getMock('Assetic\\Factory\\Resource\\ResourceInterface'); + $resource->expects($this->once()) + ->method('getContent') + ->will($this->returnValue(file_get_contents(__DIR__.'/templates/unclosed_tag.twig'))); + + $formulae = $this->loader->load($resource); + $this->assertEquals(array(), $formulae); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/TwigResourceTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/TwigResourceTest.php new file mode 100644 index 00000000..abc832df --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/TwigResourceTest.php @@ -0,0 +1,48 @@ +markTestSkipped('Twig is not installed.'); + } + } + + public function testInvalidTemplateNameGetContent() + { + $loader = $this->getMock('Twig_LoaderInterface'); + $loader->expects($this->once()) + ->method('getSource') + ->with('asdf') + ->will($this->throwException(new \Twig_Error_Loader(''))); + + $resource = new TwigResource($loader, 'asdf'); + $this->assertEquals('', $resource->getContent()); + } + + public function testInvalidTemplateNameIsFresh() + { + $loader = $this->getMock('Twig_LoaderInterface'); + $loader->expects($this->once()) + ->method('isFresh') + ->with('asdf', 1234) + ->will($this->throwException(new \Twig_Error_Loader(''))); + + $resource = new TwigResource($loader, 'asdf'); + $this->assertFalse($resource->isFresh(1234)); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/absolute_path.twig b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/absolute_path.twig new file mode 100644 index 00000000..05dc382d --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/absolute_path.twig @@ -0,0 +1,3 @@ + +{% stylesheets '/path/to/something.css' as='foo' %}{% endstylesheets %} + diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/combine.twig b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/combine.twig new file mode 100644 index 00000000..e1ab5f9e --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/combine.twig @@ -0,0 +1,3 @@ + +{% stylesheets 'foo.css' 'bar.css' filter='?foo,bar' output='css/packed.css' debug=true combine=true %}{% endstylesheets %} + diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/debug.twig b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/debug.twig new file mode 100644 index 00000000..550292ef --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/debug.twig @@ -0,0 +1,3 @@ + +{% stylesheets 'foo.css' 'bar.css' filter='?foo,bar' output='css/packed.css' debug=true %}{% endstylesheets %} + diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/filters.twig b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/filters.twig new file mode 100644 index 00000000..d2113847 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/filters.twig @@ -0,0 +1,3 @@ + +{% stylesheets 'foo' filter='foo, bar' %}{% endstylesheets %} + diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/function.twig b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/function.twig new file mode 100644 index 00000000..02841974 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/function.twig @@ -0,0 +1,3 @@ + + + diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/glob.twig b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/glob.twig new file mode 100644 index 00000000..4624933a --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/glob.twig @@ -0,0 +1,3 @@ + +{% stylesheets 'css/src/*' %}{% endstylesheets %} + diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/image.twig b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/image.twig new file mode 100644 index 00000000..902ecf0c --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/image.twig @@ -0,0 +1,3 @@ + +{% image 'images/foo.png' %}{% endimage %} + diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/mixture.twig b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/mixture.twig new file mode 100644 index 00000000..482e6ece --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/mixture.twig @@ -0,0 +1,3 @@ + +{% stylesheets 'foo' 'foo/*' '@foo' output='packed/*' name='mixture' %}{% endstylesheets %} + diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/optional_filter.twig b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/optional_filter.twig new file mode 100644 index 00000000..4a4dbb6d --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/optional_filter.twig @@ -0,0 +1,3 @@ + +{% stylesheets 'foo' filter='?foo' %}{% endstylesheets %} + diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/output_pattern.twig b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/output_pattern.twig new file mode 100644 index 00000000..cf858976 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/output_pattern.twig @@ -0,0 +1,3 @@ + +{% stylesheets 'foo' output='css/packed/*.css' %}{% endstylesheets %} + diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/output_url.twig b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/output_url.twig new file mode 100644 index 00000000..51a2a2d6 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/output_url.twig @@ -0,0 +1,3 @@ + +{% stylesheets 'foo' output='explicit_url.css' %}{% endstylesheets %} + diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/reference.twig b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/reference.twig new file mode 100644 index 00000000..371c4b7e --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/reference.twig @@ -0,0 +1,3 @@ + +{% stylesheets '@foo' %}{% endstylesheets %} + diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/unclosed_tag.twig b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/unclosed_tag.twig new file mode 100644 index 00000000..6c1d30b9 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/unclosed_tag.twig @@ -0,0 +1,5 @@ + +{% block assets %} +{% stylesheets 'foo.css' 'bar.css' filter='?foo,bar' output='css/packed.css' debug=true %} +{% endblock %} + diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/variables.twig b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/variables.twig new file mode 100644 index 00000000..5d4d2ba5 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Extension/Twig/templates/variables.twig @@ -0,0 +1,5 @@ + + {% javascripts "foo.js" "variable_input.{foo}.js" vars=["foo", "bar"] debug=true %} + {{ asset_url }} + {% endjavascripts %} + diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Factory/AssetFactoryTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Factory/AssetFactoryTest.php new file mode 100644 index 00000000..53a2da0f --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Factory/AssetFactoryTest.php @@ -0,0 +1,283 @@ +am = $this->getMock('Assetic\\AssetManager'); + $this->fm = $this->getMock('Assetic\\FilterManager'); + + $this->factory = new AssetFactory(__DIR__); + $this->factory->setAssetManager($this->am); + $this->factory->setFilterManager($this->fm); + } + + public function testNoAssetManagerReference() + { + $this->setExpectedException('LogicException', 'There is no asset manager.'); + + $factory = new AssetFactory('.'); + $factory->createAsset(array('@foo')); + } + + public function testNoAssetManagerNotReference() + { + $factory = new AssetFactory('.'); + $this->assertInstanceOf('Assetic\\Asset\\AssetInterface', $factory->createAsset(array('foo'))); + } + + public function testNoFilterManager() + { + $this->setExpectedException('LogicException', 'There is no filter manager.'); + + $factory = new AssetFactory('.'); + $factory->createAsset(array('foo'), array('foo')); + } + + public function testCreateAssetReference() + { + $referenced = $this->getMock('Assetic\\Asset\\AssetInterface'); + + $this->am->expects($this->any()) + ->method('get') + ->with('jquery') + ->will($this->returnValue($referenced)); + + $assets = $this->factory->createAsset(array('@jquery')); + $arr = iterator_to_array($assets); + $this->assertInstanceOf('Assetic\\Asset\\AssetReference', $arr[0], '->createAsset() creates a reference'); + } + + /** + * @dataProvider getHttpUrls + */ + public function testCreateHttpAsset($sourceUrl) + { + $assets = $this->factory->createAsset(array($sourceUrl)); + $arr = iterator_to_array($assets); + $this->assertInstanceOf('Assetic\\Asset\\HttpAsset', $arr[0], '->createAsset() creates an HTTP asset'); + } + + public function getHttpUrls() + { + return array( + array('http://example.com/foo.css'), + array('https://example.com/foo.css'), + array('//example.com/foo.css'), + ); + } + + public function testCreateFileAsset() + { + $assets = $this->factory->createAsset(array(basename(__FILE__))); + $arr = iterator_to_array($assets); + $this->assertInstanceOf('Assetic\\Asset\\FileAsset', $arr[0], '->createAsset() creates a file asset'); + } + + public function testCreateGlobAsset() + { + $assets = $this->factory->createAsset(array('*')); + $arr = iterator_to_array($assets); + $this->assertInstanceOf('Assetic\\Asset\\FileAsset', $arr[0], '->createAsset() uses a glob to create a file assets'); + } + + public function testCreateGlobAssetAndLoadFiles() + { + $assets = $this->factory->createAsset(array('*/Fixtures/*/*')); + $assets->load(); + + $this->assertEquals(5, count(iterator_to_array($assets)), '->createAsset() adds files'); + } + + public function testCreateGlobAssetAndExcludeDirectories() + { + $assets = $this->factory->createAsset(array('*/Fixtures/*', '*/Fixtures/*/*')); + $assets->load(); + + $this->assertEquals(5, count(iterator_to_array($assets)), '->createAsset() excludes directories and add files'); + } + + public function testCreateAssetCollection() + { + $asset = $this->factory->createAsset(array('*', basename(__FILE__))); + $this->assertInstanceOf('Assetic\\Asset\\AssetCollection', $asset, '->createAsset() creates an asset collection'); + } + + public function testFilter() + { + $this->fm->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue($this->getMock('Assetic\\Filter\\FilterInterface'))); + + $asset = $this->factory->createAsset(array(), array('foo')); + $this->assertEquals(1, count($asset->getFilters()), '->createAsset() adds filters'); + } + + public function testInvalidFilter() + { + $this->setExpectedException('InvalidArgumentException'); + + $this->fm->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->throwException(new \InvalidArgumentException())); + + $asset = $this->factory->createAsset(array(), array('foo')); + } + + public function testOptionalInvalidFilter() + { + $this->factory->setDebug(true); + + $asset = $this->factory->createAsset(array(), array('?foo')); + + $this->assertEquals(0, count($asset->getFilters()), '->createAsset() does not add an optional invalid filter'); + } + + public function testIncludingOptionalFilter() + { + $this->fm->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue($this->getMock('Assetic\\Filter\\FilterInterface'))); + + $this->factory->createAsset(array('foo.css'), array('?foo')); + } + + public function testWorkers() + { + $worker = $this->getMock('Assetic\\Factory\\Worker\\WorkerInterface'); + + // called once on the collection and once on each leaf + $worker->expects($this->exactly(3)) + ->method('process') + ->with($this->isInstanceOf('Assetic\\Asset\\AssetInterface')); + + $this->factory->addWorker($worker); + $this->factory->createAsset(array('foo.js', 'bar.js')); + } + + public function testWorkerReturn() + { + $worker = $this->getMock('Assetic\\Factory\\Worker\\WorkerInterface'); + $asset = $this->getMock('Assetic\\Asset\\AssetInterface'); + + $worker->expects($this->at(2)) + ->method('process') + ->with($this->isInstanceOf('Assetic\\Asset\\AssetCollectionInterface')) + ->will($this->returnValue($asset)); + + $this->factory->addWorker($worker); + $coll = $this->factory->createAsset(array('foo.js', 'bar.js')); + + $this->assertEquals(1, count(iterator_to_array($coll))); + } + + public function testNestedFormula() + { + $this->fm->expects($this->once()) + ->method('get') + ->with('foo') + ->will($this->returnValue($this->getMock('Assetic\\Filter\\FilterInterface'))); + + $inputs = array( + 'css/main.css', + array( + // nested formula + array('css/more.sass'), + array('foo'), + ), + ); + + $asset = $this->factory->createAsset($inputs, array(), array('output' => 'css/*.css')); + + $i = 0; + foreach ($asset as $leaf) { + $i++; + } + + $this->assertEquals(2, $i); + } + + public function testGetLastModified() + { + $asset = $this->getMock('Assetic\Asset\AssetInterface'); + $child = $this->getMock('Assetic\Asset\AssetInterface'); + $filter1 = $this->getMock('Assetic\Filter\FilterInterface'); + $filter2 = $this->getMock('Assetic\Filter\DependencyExtractorInterface'); + + $asset->expects($this->any()) + ->method('getLastModified') + ->will($this->returnValue(123)); + $asset->expects($this->any()) + ->method('getFilters') + ->will($this->returnValue(array($filter1, $filter2))); + $asset->expects($this->once()) + ->method('ensureFilter') + ->with($filter1); + $filter2->expects($this->once()) + ->method('getChildren') + ->with($this->factory) + ->will($this->returnValue(array($child))); + $child->expects($this->any()) + ->method('getLastModified') + ->will($this->returnValue(456)); + $child->expects($this->any()) + ->method('getFilters') + ->will($this->returnValue(array())); + + $this->assertEquals(456, $this->factory->getLastModified($asset)); + } + + public function testGetLastModifiedCollection() + { + $leaf = $this->getMock('Assetic\Asset\AssetInterface'); + $child = $this->getMock('Assetic\Asset\AssetInterface'); + $filter1 = $this->getMock('Assetic\Filter\FilterInterface'); + $filter2 = $this->getMock('Assetic\Filter\DependencyExtractorInterface'); + + $asset = new AssetCollection(); + $asset->add($leaf); + + $leaf->expects($this->any()) + ->method('getLastModified') + ->will($this->returnValue(123)); + $leaf->expects($this->any()) + ->method('getFilters') + ->will($this->returnValue(array($filter1, $filter2))); + $leaf->expects($this->once()) + ->method('ensureFilter') + ->with($filter1); + $filter2->expects($this->once()) + ->method('getChildren') + ->with($this->factory) + ->will($this->returnValue(array($child))); + $child->expects($this->any()) + ->method('getLastModified') + ->will($this->returnValue(456)); + $child->expects($this->any()) + ->method('getFilters') + ->will($this->returnValue(array())); + + $this->assertEquals(456, $this->factory->getLastModified($asset)); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Factory/LazyAssetManagerTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Factory/LazyAssetManagerTest.php new file mode 100644 index 00000000..2aaccc3c --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Factory/LazyAssetManagerTest.php @@ -0,0 +1,107 @@ +factory = $this->getMockBuilder('Assetic\\Factory\\AssetFactory') + ->disableOriginalConstructor() + ->getMock(); + + $this->am = new LazyAssetManager($this->factory); + } + + public function testGetFromLoader() + { + $resource = $this->getMock('Assetic\\Factory\\Resource\\ResourceInterface'); + $loader = $this->getMock('Assetic\\Factory\\Loader\\FormulaLoaderInterface'); + $asset = $this->getMock('Assetic\\Asset\\AssetInterface'); + + $formula = array( + array('js/core.js', 'js/more.js'), + array('?yui_js'), + array('output' => 'js/all.js') + ); + + $loader->expects($this->once()) + ->method('load') + ->with($resource) + ->will($this->returnValue(array('foo' => $formula))); + $this->factory->expects($this->once()) + ->method('createAsset') + ->with($formula[0], $formula[1], $formula[2] + array('name' => 'foo')) + ->will($this->returnValue($asset)); + + $this->am->setLoader('foo', $loader); + $this->am->addResource($resource, 'foo'); + + $this->assertSame($asset, $this->am->get('foo'), '->get() returns an asset from the loader'); + + // test the "once" expectations + $this->am->get('foo'); + } + + public function testGetResources() + { + $resources = array( + $this->getMock('Assetic\\Factory\\Resource\\ResourceInterface'), + $this->getMock('Assetic\\Factory\\Resource\\ResourceInterface'), + ); + + $this->am->addResource($resources[0], 'foo'); + $this->am->addResource($resources[1], 'bar'); + + $ret = $this->am->getResources(); + + foreach ($resources as $resource) { + $this->assertTrue(in_array($resource, $ret, true)); + } + } + + public function testGetResourcesEmpty() + { + $this->am->getResources(); + } + + public function testSetFormula() + { + $this->am->setFormula('foo', array()); + $this->am->load(); + $this->assertTrue($this->am->hasFormula('foo'), '->load() does not remove manually added formulae'); + } + + public function testIsDebug() + { + $this->factory->expects($this->once()) + ->method('isDebug') + ->will($this->returnValue(false)); + + $this->assertSame(false, $this->am->isDebug(), '->isDebug() proxies the factory'); + } + + public function testGetLastModified() + { + $asset = $this->getMock('Assetic\Asset\AssetInterface'); + + $this->factory->expects($this->once()) + ->method('getLastModified') + ->will($this->returnValue(123)); + + $this->assertSame(123, $this->am->getLastModified($asset)); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Factory/Loader/CachedFormulaLoaderTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Factory/Loader/CachedFormulaLoaderTest.php new file mode 100644 index 00000000..a56dbeff --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Factory/Loader/CachedFormulaLoaderTest.php @@ -0,0 +1,138 @@ +loader = $this->getMock('Assetic\\Factory\\Loader\\FormulaLoaderInterface'); + $this->configCache = $this->getMockBuilder('Assetic\\Cache\\ConfigCache') + ->disableOriginalConstructor() + ->getMock(); + $this->resource = $this->getMock('Assetic\\Factory\\Resource\\ResourceInterface'); + } + + public function testNotDebug() + { + $expected = array( + 'foo' => array(array(), array(), array()), + 'bar' => array(array(), array(), array()), + ); + + $this->configCache->expects($this->once()) + ->method('has') + ->with($this->isType('string')) + ->will($this->returnValue(false)); + $this->loader->expects($this->once()) + ->method('load') + ->with($this->resource) + ->will($this->returnValue($expected)); + $this->configCache->expects($this->once()) + ->method('set') + ->with($this->isType('string'), $expected); + + $loader = new CachedFormulaLoader($this->loader, $this->configCache); + $this->assertEquals($expected, $loader->load($this->resource), '->load() returns formulae'); + } + + public function testNotDebugCached() + { + $expected = array( + 'foo' => array(array(), array(), array()), + 'bar' => array(array(), array(), array()), + ); + + $this->configCache->expects($this->once()) + ->method('has') + ->with($this->isType('string')) + ->will($this->returnValue(true)); + $this->resource->expects($this->never()) + ->method('isFresh'); + $this->configCache->expects($this->once()) + ->method('get') + ->with($this->isType('string')) + ->will($this->returnValue($expected)); + + $loader = new CachedFormulaLoader($this->loader, $this->configCache); + $this->assertEquals($expected, $loader->load($this->resource), '->load() returns formulae'); + } + + public function testDebugCached() + { + $timestamp = 123; + $expected = array( + 'foo' => array(array(), array(), array()), + 'bar' => array(array(), array(), array()), + ); + + $this->configCache->expects($this->once()) + ->method('has') + ->with($this->isType('string')) + ->will($this->returnValue(true)); + $this->configCache->expects($this->once()) + ->method('getTimestamp') + ->with($this->isType('string')) + ->will($this->returnValue($timestamp)); + $this->resource->expects($this->once()) + ->method('isFresh') + ->with($timestamp) + ->will($this->returnValue(true)); + $this->loader->expects($this->never()) + ->method('load'); + $this->configCache->expects($this->once()) + ->method('get') + ->with($this->isType('string')) + ->will($this->returnValue($expected)); + + $loader = new CachedFormulaLoader($this->loader, $this->configCache, true); + $this->assertEquals($expected, $loader->load($this->resource), '->load() returns formulae'); + } + + public function testDebugCachedStale() + { + $timestamp = 123; + $expected = array( + 'foo' => array(array(), array(), array()), + 'bar' => array(array(), array(), array()), + ); + + $this->configCache->expects($this->once()) + ->method('has') + ->with($this->isType('string')) + ->will($this->returnValue(true)); + $this->configCache->expects($this->once()) + ->method('getTimestamp') + ->with($this->isType('string')) + ->will($this->returnValue($timestamp)); + $this->resource->expects($this->once()) + ->method('isFresh') + ->with($timestamp) + ->will($this->returnValue(false)); + $this->loader->expects($this->once()) + ->method('load') + ->with($this->resource) + ->will($this->returnValue($expected)); + $this->configCache->expects($this->once()) + ->method('set') + ->with($this->isType('string'), $expected); + + $loader = new CachedFormulaLoader($this->loader, $this->configCache, true); + $this->assertEquals($expected, $loader->load($this->resource), '->load() returns formulae'); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Factory/Loader/FunctionCallsFormulaLoaderTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Factory/Loader/FunctionCallsFormulaLoaderTest.php new file mode 100644 index 00000000..6e98e4ff --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Factory/Loader/FunctionCallsFormulaLoaderTest.php @@ -0,0 +1,69 @@ +getMock('Assetic\\Factory\\Resource\\ResourceInterface'); + $factory = $this->getMockBuilder('Assetic\\Factory\\AssetFactory') + ->disableOriginalConstructor() + ->getMock(); + + $resource->expects($this->once()) + ->method('getContent') + ->will($this->returnValue('')); + $factory->expects($this->once()) + ->method('generateAssetName') + ->will($this->returnValue($name)); + + $loader = new FunctionCallsFormulaLoader($factory); + $formulae = $loader->load($resource); + + $this->assertEquals($expected, $formulae); + } + + public function getJavascriptInputs() + { + return array( + array('assetic_javascripts', '"js/core.js"', 'asdf', array('asdf' => array(array('js/core.js'), array(), array('debug' => false, 'output' => 'js/*.js', 'name' => 'asdf', )))), + array('assetic_javascripts', "'js/core.js'", 'asdf', array('asdf' => array(array('js/core.js'), array(), array('debug' => false, 'output' => 'js/*.js', 'name' => 'asdf', )))), + array('assetic_javascripts', "array('js/core.js')", 'asdf', array('asdf' => array(array('js/core.js'), array(), array('debug' => false, 'output' => 'js/*.js', 'name' => 'asdf', )))), + array('assetic_javascripts', 'array("js/core.js")', 'asdf', array('asdf' => array(array('js/core.js'), array(), array('debug' => false, 'output' => 'js/*.js', 'name' => 'asdf', )))), + array('assetic_image', '"images/logo.gif"', 'asdf', array('asdf' => array(array('images/logo.gif'), array(), array('debug' => false, 'output' => 'images/*', 'name' => 'asdf')))), + ); + } + + public function testComplexFormula() + { + $factory = new AssetFactory(__DIR__.'/templates', true); + $loader = new FunctionCallsFormulaLoader($factory); + $resource = new FileResource(__DIR__.'/templates/debug.php'); + $formulae = $loader->load($resource); + + $this->assertEquals(array( + 'test123' => array( + array('foo.css', 'bar.css'), + array('?foo', 'bar'), + array('name' => 'test123', 'output' => 'css/packed.css', 'debug' => true), + ), + ), $formulae); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Factory/Loader/templates/debug.php b/kriswallsmith/assetic/tests/Assetic/Test/Factory/Loader/templates/debug.php new file mode 100644 index 00000000..750c25d8 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Factory/Loader/templates/debug.php @@ -0,0 +1,8 @@ + + 'test123', 'output' => 'css/packed.css', 'debug' => true)) as $url): ?> + + + diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Factory/Resource/CoalescingDirectoryResourceTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Factory/Resource/CoalescingDirectoryResourceTest.php new file mode 100644 index 00000000..c8141245 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Factory/Resource/CoalescingDirectoryResourceTest.php @@ -0,0 +1,42 @@ +assertEquals(array( + realpath(__DIR__.'/Fixtures/dir1/file1.txt'), + realpath(__DIR__.'/Fixtures/dir1/file2.txt'), + realpath(__DIR__.'/Fixtures/dir2/file3.txt'), + ), $paths, 'files from multiple directories are merged'); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Factory/Resource/DirectoryResourceTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Factory/Resource/DirectoryResourceTest.php new file mode 100644 index 00000000..fa8f0a77 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Factory/Resource/DirectoryResourceTest.php @@ -0,0 +1,136 @@ +assertTrue($resource->isFresh(time() + 5)); + $this->assertFalse($resource->isFresh(0)); + } + + /** + * @dataProvider getPatterns + */ + public function testGetContent($pattern) + { + $resource = new DirectoryResource(__DIR__, $pattern); + $content = $resource->getContent(); + + $this->assertInternalType('string', $content); + } + + public function getPatterns() + { + return array( + array(null), + array('/\.php$/'), + array('/\.foo$/'), + ); + } + + /** + * @dataProvider getPatternsAndEmpty + */ + public function testIteration($pattern, $empty) + { + $resource = new DirectoryResource(__DIR__, $pattern); + + $count = 0; + foreach ($resource as $r) { + ++$count; + $this->assertInstanceOf('Assetic\\Factory\\Resource\\ResourceInterface', $r); + } + + if ($empty) { + $this->assertEmpty($count); + } else { + $this->assertNotEmpty($count); + } + } + + public function getPatternsAndEmpty() + { + return array( + array(null, false), + array('/\.php$/', false), + array('/\.foo$/', true), + ); + } + + public function testRecursiveIteration() + { + $resource = new DirectoryResource(realpath(__DIR__.'/..'), '/^'.preg_quote(basename(__FILE__)).'$/'); + + $count = 0; + foreach ($resource as $r) { + ++$count; + } + + $this->assertEquals(1, $count); + } + + /** + * @dataProvider getPaths + */ + public function testTrailingSlash($path) + { + $resource = new DirectoryResource($path); + $this->assertStringEndsWith(DIRECTORY_SEPARATOR, (string) $resource, 'path ends with a slash'); + } + + public function getPaths() + { + return array( + array(__DIR__), + array(__DIR__.DIRECTORY_SEPARATOR), + ); + } + + public function testInvalidDirectory() + { + $resource = new DirectoryResource(__DIR__.'foo'); + $this->assertEquals(0, iterator_count($resource), 'works for non-existent directory'); + } + + public function testFollowSymlinks() + { + // Create the symlink if it doesn't already exist yet (if someone broke the entire testsuite perhaps) + if (!is_dir(__DIR__.'/Fixtures/dir3')) { + symlink(__DIR__.'/Fixtures/dir2', __DIR__.'/Fixtures/dir3'); + } + + $resource = new DirectoryResource(__DIR__.'/Fixtures'); + + $count = 0; + foreach ($resource as $r) { + ++$count; + } + + $this->assertEquals(7, $count); + } + + protected function tearDown() + { + if (is_dir(__DIR__.'/Fixtures/dir3') && is_link(__DIR__.'/Fixtures/dir3')) { + if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + rmdir(__DIR__.'/Fixtures/dir3'); + } else { + unlink(__DIR__.'/Fixtures/dir3'); + } + } + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Factory/Resource/FileResourceTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Factory/Resource/FileResourceTest.php new file mode 100644 index 00000000..0770bf1d --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Factory/Resource/FileResourceTest.php @@ -0,0 +1,42 @@ +assertTrue($resource->isFresh(time() + 5)); + $this->assertFalse($resource->isFresh(0)); + } + + public function testGetContent() + { + $resource = new FileResource(__FILE__); + $this->assertEquals(file_get_contents(__FILE__), $resource->getContent()); + } + + public function testIsFreshOnInvalidPath() + { + $resource = new FileResource(__FILE__.'foo'); + $this->assertFalse($resource->isFresh(time()), '->isFresh() returns false if the file does not exist'); + } + + public function testGetContentOnInvalidPath() + { + $resource = new FileResource(__FILE__.'foo'); + $this->assertSame('', $resource->getContent(), '->getContent() returns an empty string when path is invalid'); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Factory/Resource/Fixtures/css/style.css b/kriswallsmith/assetic/tests/Assetic/Test/Factory/Resource/Fixtures/css/style.css new file mode 100644 index 00000000..05a42e29 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Factory/Resource/Fixtures/css/style.css @@ -0,0 +1 @@ +body{color:#222;background:#fff;} \ No newline at end of file diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Factory/Resource/Fixtures/dir1/file1.txt b/kriswallsmith/assetic/tests/Assetic/Test/Factory/Resource/Fixtures/dir1/file1.txt new file mode 100644 index 00000000..e69de29b diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Factory/Resource/Fixtures/dir1/file2.txt b/kriswallsmith/assetic/tests/Assetic/Test/Factory/Resource/Fixtures/dir1/file2.txt new file mode 100644 index 00000000..e69de29b diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Factory/Resource/Fixtures/dir2/file1.txt b/kriswallsmith/assetic/tests/Assetic/Test/Factory/Resource/Fixtures/dir2/file1.txt new file mode 100644 index 00000000..e69de29b diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Factory/Resource/Fixtures/dir2/file3.txt b/kriswallsmith/assetic/tests/Assetic/Test/Factory/Resource/Fixtures/dir2/file3.txt new file mode 100644 index 00000000..e69de29b diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Factory/Worker/CacheBustingWorkerTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Factory/Worker/CacheBustingWorkerTest.php new file mode 100644 index 00000000..5024970f --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Factory/Worker/CacheBustingWorkerTest.php @@ -0,0 +1,115 @@ +worker = new CacheBustingWorker(); + } + + protected function tearDown() + { + $this->worker = null; + } + + /** + * @test + */ + public function shouldApplyHash() + { + $asset = $this->getMock('Assetic\Asset\AssetInterface'); + $factory = $this->getMockBuilder('Assetic\Factory\AssetFactory') + ->disableOriginalConstructor() + ->getMock(); + + $asset->expects($this->any()) + ->method('getTargetPath') + ->will($this->returnValue('css/main.css')); + $factory->expects($this->any()) + ->method('getLastModified') + ->will($this->returnValue(1234)); + $asset->expects($this->once()) + ->method('setTargetPath') + ->with($this->logicalAnd( + $this->stringStartsWith('css/main-'), + $this->stringEndsWith('.css') + )); + + $this->worker->process($asset, $factory); + } + + /** + * @test + */ + public function shouldApplyConsistentHash() + { + $asset = $this->getMock('Assetic\Asset\AssetInterface'); + $factory = $this->getMockBuilder('Assetic\Factory\AssetFactory') + ->disableOriginalConstructor() + ->getMock(); + $paths = array(); + + $asset->expects($this->any()) + ->method('getTargetPath') + ->will($this->returnValue('css/main.css')); + $factory->expects($this->any()) + ->method('getLastModified') + ->will($this->returnValue(1234)); + $asset->expects($this->exactly(2)) + ->method('setTargetPath') + ->will($this->returnCallback(function($path) use(& $paths) { + $paths[] = $path; + })); + + $this->worker->process($asset, $factory); + $this->worker->process($asset, $factory); + + $this->assertCount(2, $paths); + $this->assertCount(1, array_unique($paths)); + } + + /** + * @test + */ + public function shouldNotReapplyHash() + { + $asset = $this->getMock('Assetic\Asset\AssetInterface'); + $factory = $this->getMockBuilder('Assetic\Factory\AssetFactory') + ->disableOriginalConstructor() + ->getMock(); + $path = null; + + $asset->expects($this->any()) + ->method('getTargetPath') + ->will($this->returnCallback(function() use(& $path) { + return $path ?: 'css/main.css'; + })); + $factory->expects($this->any()) + ->method('getLastModified') + ->will($this->returnValue(1234)); + $asset->expects($this->once()) + ->method('setTargetPath') + ->will($this->returnCallback(function($arg) use(& $path) { + $path = $arg; + })); + + $this->worker->process($asset, $factory); + $this->worker->process($asset, $factory); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Factory/Worker/EnsureFilterWorkerTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Factory/Worker/EnsureFilterWorkerTest.php new file mode 100644 index 00000000..a68acf9d --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Factory/Worker/EnsureFilterWorkerTest.php @@ -0,0 +1,53 @@ +getMock('Assetic\\Filter\\FilterInterface'); + $asset = $this->getMock('Assetic\\Asset\\AssetInterface'); + $factory = $this->getMockBuilder('Assetic\Factory\AssetFactory') + ->disableOriginalConstructor() + ->getMock(); + + $asset->expects($this->once()) + ->method('getTargetPath') + ->will($this->returnValue('css/main.css')); + $asset->expects($this->once()) + ->method('ensureFilter') + ->with($filter); + + $worker = new EnsureFilterWorker('/\.css$/', $filter); + $worker->process($asset, $factory); + } + + public function testNonMatch() + { + $filter = $this->getMock('Assetic\\Filter\\FilterInterface'); + $asset = $this->getMock('Assetic\\Asset\\AssetInterface'); + $factory = $this->getMockBuilder('Assetic\Factory\AssetFactory') + ->disableOriginalConstructor() + ->getMock(); + + $asset->expects($this->once()) + ->method('getTargetPath') + ->will($this->returnValue('js/all.js')); + $asset->expects($this->never())->method('ensureFilter'); + + $worker = new EnsureFilterWorker('/\.css$/', $filter); + $worker->process($asset, $factory); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/BaseProcessFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/BaseProcessFilterTest.php new file mode 100644 index 00000000..4393604c --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/BaseProcessFilterTest.php @@ -0,0 +1,35 @@ +assertInstanceOf('Assetic\\Filter\\FilterInterface', $filter, 'BaseProcessFilter implements FilterInterface'); + } +} + +class BaseProcessFilterFilter extends BaseProcessFilter +{ + public function filterLoad(AssetInterface $asset) + { + } + + public function filterDump(AssetInterface $asset) + { + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/CallablesFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/CallablesFilterTest.php new file mode 100644 index 00000000..e5adb1a9 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/CallablesFilterTest.php @@ -0,0 +1,69 @@ +assertInstanceOf('Assetic\\Filter\\FilterInterface', $filter, 'CallablesFilter implements FilterInterface'); + $this->assertInstanceOf('Assetic\\Filter\\DependencyExtractorInterface', $filter, 'CallablesFilter implements DependencyExtractorInterface'); + } + + public function testLoader() + { + $nb = 0; + $filter = new CallablesFilter(function($asset) use (&$nb) { $nb++; }); + $filter->filterLoad($this->getMock('Assetic\\Asset\\AssetInterface')); + $this->assertEquals(1, $nb, '->filterLoad() calls the loader callable'); + } + + public function testDumper() + { + $nb = 0; + $filter = new CallablesFilter(null, function($asset) use (&$nb) { $nb++; }); + $filter->filterDump($this->getMock('Assetic\\Asset\\AssetInterface')); + $this->assertEquals(1, $nb, '->filterDump() calls the loader callable'); + } + + public function testDependencyExtractor() + { + + $nb = 0; + $self = $this; + $assetFactoryMock = $this->getMockBuilder('Assetic\\Factory\\AssetFactory') + ->disableOriginalConstructor() + ->getMock(); + + $result = array(new StringAsset("test")); + + $filter = new CallablesFilter(null, null, function(AssetFactory $factory, $content, $loadPath) use (&$nb, $assetFactoryMock, $self, $result) { + $self->assertSame($factory, $assetFactoryMock, '-> the asset factory is passed to the callable'); + $self->assertEquals('content', $content, '-> the content is passed to the callable'); + $self->assertEquals('loadPath', $loadPath, '-> the load path is passed to the callable'); + $nb++; + return $result; + }); + + $r = $filter->getChildren($assetFactoryMock, 'content', 'loadPath'); + $this->assertEquals($result, $r, "->getChildren() returns the callable's result"); + $this->assertEquals(1, $nb, '->getChildren() calls the extractor callable'); + + $filter = new CallablesFilter(); + $this->assertEquals(array(), $filter->getChildren($assetFactoryMock, 'ignored', 'ignored'), '-> without an extractor callable, the filter just returns an empty array (of assets)'); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/CoffeeScriptFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/CoffeeScriptFilterTest.php new file mode 100644 index 00000000..18fb3d1f --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/CoffeeScriptFilterTest.php @@ -0,0 +1,81 @@ +findExecutable('coffee', 'COFFEE_BIN'); + $nodeBin = $this->findExecutable('node', 'NODE_BIN'); + + if (!$coffeeBin) { + $this->markTestSkipped('Unable to find `coffee` executable.'); + } + + $this->filter = new CoffeeScriptFilter($coffeeBin, $nodeBin); + } + + public function testFilterLoad() + { + $expected = << x * x'); + $asset->load(); + + $this->filter->filterLoad($asset); + + $this->assertEquals($expected, $this->clean($asset->getContent())); + } + + public function testBare() + { + $expected = << x * x'); + $asset->load(); + + $this->filter->setBare(true); + $this->filter->filterLoad($asset); + + $this->assertEquals($expected, $this->clean($asset->getContent())); + } + + private function clean($js) + { + return preg_replace('~^//.*\n\s*~m', '', $js); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/CompassFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/CompassFilterTest.php new file mode 100644 index 00000000..25e2fc5a --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/CompassFilterTest.php @@ -0,0 +1,72 @@ + + * @group integration + */ +class CompassFilterTest extends FilterTestCase +{ + private $filter; + + protected function setUp() + { + if (!$compassBin = $this->findExecutable('compass', 'COMPASS_BIN')) { + $this->markTestSkipped('Unable to find `compass` executable.'); + } + + $this->filter = new CompassFilter($compassBin); + } + + protected function tearDown() + { + $this->filter = null; + } + + public function testFilterLoadWithScss() + { + $asset = new FileAsset(__DIR__.'/fixtures/compass/stylesheet.scss'); + $asset->load(); + + $this->filter->filterLoad($asset); + + $this->assertContains('.test-class', $asset->getContent()); + $this->assertContains('font-size: 2em;', $asset->getContent()); + } + + public function testFilterLoadWithSass() + { + $asset = new FileAsset(__DIR__.'/fixtures/compass/stylesheet.sass'); + $asset->load(); + + $this->filter->filterLoad($asset); + + $this->assertContains('.test-class', $asset->getContent()); + $this->assertContains('font-size: 2em;', $asset->getContent()); + } + + public function testCompassMixin() + { + $asset = new FileAsset(__DIR__.'/fixtures/compass/compass.sass'); + $asset->load(); + + $this->filter->filterLoad($asset); + + $this->assertContains('text-decoration', $asset->getContent()); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/CssEmbedFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/CssEmbedFilterTest.php new file mode 100644 index 00000000..98d8b402 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/CssEmbedFilterTest.php @@ -0,0 +1,65 @@ +findExecutable('java', 'JAVA_BIN')) { + $this->markTestSkipped('Unable to find `java` executable.'); + } + + if (!isset($_SERVER['CSSEMBED_JAR'])) { + $this->markTestSkipped('There is no CSSEMBED_JAR environment variable.'); + } + + $this->filter = new CssEmbedFilter($_SERVER['CSSEMBED_JAR'], $javaBin); + } + + protected function tearDown() + { + $this->filter = null; + } + + public function testCssEmbedDataUri() + { + $data = base64_encode(file_get_contents(__DIR__.'/fixtures/home.png')); + + $asset = new FileAsset(__DIR__ . '/fixtures/cssembed/test.css'); + $asset->load(); + + $this->filter->filterDump($asset); + + $this->assertContains('url(data:image/png;base64,'.$data, $asset->getContent()); + } + + public function testCssEmbedMhtml() + { + $asset = new FileAsset(__DIR__ . '/fixtures/cssembed/test.css'); + $asset->load(); + + $this->filter->setMhtml(true); + $this->filter->setMhtmlRoot('/test'); + $this->filter->filterDump($asset); + + $this->assertContains('url(mhtml:/test/!', $asset->getContent()); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/CssImportFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/CssImportFilterTest.php new file mode 100644 index 00000000..e68fe122 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/CssImportFilterTest.php @@ -0,0 +1,67 @@ +setTargetPath('foo/bar.css'); + $asset->ensureFilter($filter1); + $asset->ensureFilter($filter2); + + $expected = <<assertEquals($expected, $asset->dump(), '->filterLoad() inlines CSS imports'); + } + + /** + * The order of these two filters is only interchangeable because one acts on + * load and the other on dump. We need a more scalable solution. + */ + public function getFilters() + { + return array( + array(new CssImportFilter(), new CssRewriteFilter()), + array(new CssRewriteFilter(), new CssImportFilter()), + ); + } + + public function testNonCssImport() + { + $asset = new FileAsset(__DIR__.'/fixtures/cssimport/noncssimport.css', array(), __DIR__.'/fixtures/cssimport', 'noncssimport.css'); + $asset->load(); + + $filter = new CssImportFilter(); + $filter->filterLoad($asset); + + $this->assertEquals(file_get_contents(__DIR__.'/fixtures/cssimport/noncssimport.css'), $asset->getContent(), '->filterLoad() skips non css'); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/CssMinFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/CssMinFilterTest.php new file mode 100644 index 00000000..0e3242b1 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/CssMinFilterTest.php @@ -0,0 +1,40 @@ +markTestSkipped('CssMin is not installed.'); + } + } + + public function testRelativeSourceUrlImportImports() + { + $asset = new FileAsset(__DIR__.'/fixtures/cssmin/main.css'); + $asset->load(); + + $filter = new CssMinFilter(__DIR__.'/fixtures/cssmin'); + $filter->setFilter('ImportImports', true); + $filter->filterDump($asset); + + $this->assertEquals('body{color:white}body{background:black}', $asset->getContent()); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/CssRewriteFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/CssRewriteFilterTest.php new file mode 100644 index 00000000..16c69358 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/CssRewriteFilterTest.php @@ -0,0 +1,151 @@ +setTargetPath($targetPath); + $asset->load(); + + $filter = new CssRewriteFilter(); + $filter->filterLoad($asset); + $filter->filterDump($asset); + + $this->assertEquals(sprintf($format, $expectedUrl), $asset->getContent(), '->filterDump() rewrites relative urls'); + } + + public function provideUrls() + { + return array( + // url variants + array('body { background: url(%s); }', 'css/body.css', 'css/build/main.css', '../images/bg.gif', '../../images/bg.gif'), + array('body { background: url("%s"); }', 'css/body.css', 'css/build/main.css', '../images/bg.gif', '../../images/bg.gif'), + array('body { background: url(\'%s\'); }', 'css/body.css', 'css/build/main.css', '../images/bg.gif', '../../images/bg.gif'), + + //url with data: + array('body { background: url(\'%s\'); }', 'css/body.css', 'css/build/main.css', 'data:image/png;base64,abcdef=', 'data:image/png;base64,abcdef='), + array('body { background: url(\'%s\'); }', 'css/body.css', 'css/build/main.css', '../images/bg-data:.gif', '../../images/bg-data:.gif'), + + // @import variants + array('@import "%s";', 'css/imports.css', 'css/build/main.css', 'import.css', '../import.css'), + array('@import url(%s);', 'css/imports.css', 'css/build/main.css', 'import.css', '../import.css'), + array('@import url("%s");', 'css/imports.css', 'css/build/main.css', 'import.css', '../import.css'), + array('@import url(\'%s\');', 'css/imports.css', 'css/build/main.css', 'import.css', '../import.css'), + + // path diffs + array('body { background: url(%s); }', 'css/body/bg.css', 'css/build/main.css', '../../images/bg.gif', '../../images/bg.gif'), + array('body { background: url(%s); }', 'css/body.css', 'main.css', '../images/bg.gif', 'images/bg.gif'), + array('body { background: url(%s); }', 'body.css', 'css/main.css', 'images/bg.gif', '../images/bg.gif'), + array('body { background: url(%s); }', 'source/css/body.css', 'output/build/main.css', '../images/bg.gif', '../../source/images/bg.gif'), + array('body { background: url(%s); }', 'css/body.css', 'css/build/main.css', '//example.com/images/bg.gif', '//example.com/images/bg.gif'), + + // url diffs + array('body { background: url(%s); }', 'css/body.css', 'css/build/main.css', 'http://foo.com/bar.gif', 'http://foo.com/bar.gif'), + array('body { background: url(%s); }', 'css/body.css', 'css/build/main.css', '/images/foo.gif', '/images/foo.gif'), + array('body { background: url(%s); }', 'css/body.css', 'css/build/main.css', 'http://foo.com/images/foo.gif', 'http://foo.com/images/foo.gif'), + + // IE AlphaImageLoader filter + array('.fix { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'%s\'); }', 'css/ie.css', 'css/build/ie.css', '../images/fix.png', '../../images/fix.png'), + ); + } + + /** + * @dataProvider provideMultipleUrls + */ + public function testMultipleUrls($format, $sourcePath, $targetPath, $inputUrl1, $inputUrl2, $expectedUrl1, $expectedUrl2) + { + $asset = new StringAsset(sprintf($format, $inputUrl1, $inputUrl2), array(), null, $sourcePath); + $asset->setTargetPath($targetPath); + $asset->load(); + + $filter = new CssRewriteFilter(); + $filter->filterLoad($asset); + $filter->filterDump($asset); + + $this->assertEquals(sprintf($format, $expectedUrl1, $expectedUrl2), $asset->getContent(), '->filterDump() rewrites relative urls'); + } + + public function provideMultipleUrls() + { + return array( + // multiple url + array('body { background: url(%s); background: url(%s); }', 'css/body.css', 'css/build/main.css', '../images/bg.gif', '../images/bg2.gif', '../../images/bg.gif', '../../images/bg2.gif'), + array("body { background: url(%s);\nbackground: url(%s); }", 'css/body.css', 'css/build/main.css', '../images/bg.gif', '../images/bg2.gif', '../../images/bg.gif', '../../images/bg2.gif'), + + // multiple import + array('@import "%s"; @import "%s";', 'css/imports.css', 'css/build/main.css', 'import.css', 'import2.css', '../import.css', '../import2.css'), + array("@import \"%s\";\n@import \"%s\";", 'css/imports.css', 'css/build/main.css', 'import.css', 'import2.css', '../import.css', '../import2.css'), + + // mixed urls and imports + array('@import "%s"; body { background: url(%s); }', 'css/body.css', 'css/build/main.css', 'import.css', '../images/bg2.gif', '../import.css', '../../images/bg2.gif'), + array("@import \"%s\";\nbody { background: url(%s); }", 'css/body.css', 'css/build/main.css', 'import.css', '../images/bg2.gif', '../import.css', '../../images/bg2.gif'), + ); + } + + public function testNoTargetPath() + { + $content = 'body { background: url(foo.gif); }'; + + $asset = new StringAsset($content); + $asset->load(); + + $filter = new CssRewriteFilter(); + $filter->filterDump($asset); + + $this->assertEquals($content, $asset->getContent(), '->filterDump() urls are not changed without urls'); + } + + public function testExternalSource() + { + $asset = new StringAsset('body { background: url(../images/bg.gif); }', array(), 'http://www.example.com', 'css/main.css'); + $asset->setTargetPath('css/packed/main.css'); + $asset->load(); + + $filter = new CssRewriteFilter(); + $filter->filterDump($asset); + + $this->assertContains('http://www.example.com/images/bg.gif', $asset->getContent(), '->filterDump() rewrites references in external stylesheets'); + } + + public function testEmptySrcAttributeSelector() + { + $asset = new StringAsset('img[src=""] { border: red; }', array(), 'http://www.example.com', 'css/main.css'); + $asset->setTargetPath('css/packed/main.css'); + $asset->load(); + + $filter = new CssRewriteFilter(); + $filter->filterDump($asset); + + // no error is thrown + } + + public function testEmptyUrl() + { + $asset = new StringAsset('body { background: url(); }', array(), 'http://www.example.com', 'css/main.css'); + $asset->setTargetPath('css/packed/main.css'); + $asset->load(); + + $filter = new CssRewriteFilter(); + $filter->filterDump($asset); + + // no error is thrown + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/DartFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/DartFilterTest.php new file mode 100644 index 00000000..6cd0d060 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/DartFilterTest.php @@ -0,0 +1,61 @@ +findExecutable('dart2js', 'DART_BIN')) { + $this->markTestSkipped('Unable to find `dart2js` executable.'); + } + + $this->filter = new DartFilter($dartBin); + } + + protected function tearDown() + { + $this->filter = null; + } + + public function testFilterLoad() + { + $input = << msg.text = 'Dart!'); + document.body.nodes.add(btn); +} +EOM; + + $asset = new StringAsset($input); + $asset->load(); + + $this->filter->filterLoad($asset); + + $this->assertContains( + 'Generated by dart2js, the Dart to JavaScript compiler', + $asset->getContent() + ); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/EmberPrecompileFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/EmberPrecompileFilterTest.php new file mode 100644 index 00000000..4455769c --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/EmberPrecompileFilterTest.php @@ -0,0 +1,55 @@ +findExecutable('ember-precompile', 'EMBERPRECOMPILE_BIN'); + $nodeBin = $this->findExecutable('node', 'NODE_BIN'); + + if (!$emberBin) { + $this->markTestSkipped('Unable to find `ember-precompile` executable.'); + } + + $this->asset = new FileAsset(__DIR__.'/fixtures/handlebars/template.handlebars'); + $this->asset->load(); + + $this->filter = new EmberPrecompileFilter($emberBin, $nodeBin); + } + + protected function tearDown() + { + $this->asset = null; + $this->filter = null; + } + + public function testEmberPrecompile() + { + $this->filter->filterLoad($this->asset); + + $this->assertNotContains('{{ var }}', $this->asset->getContent()); + + $this->assertContains('Ember.TEMPLATES["template"]', $this->asset->getContent()); + $this->assertContains('data.buffer.push("

");', $this->asset->getContent()); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/FilterCollectionTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/FilterCollectionTest.php new file mode 100644 index 00000000..8437c092 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/FilterCollectionTest.php @@ -0,0 +1,59 @@ +assertInstanceOf('Assetic\\Filter\\FilterInterface', $filter, 'FilterCollection implements FilterInterface'); + } + + public function testEnsure() + { + $filter = $this->getMock('Assetic\\Filter\\FilterInterface'); + $asset = $this->getMock('Assetic\\Asset\\AssetInterface'); + + $filter->expects($this->once())->method('filterLoad'); + + $coll = new FilterCollection(); + $coll->ensure($filter); + $coll->ensure($filter); + $coll->filterLoad($asset); + } + + public function testAll() + { + $filter = new FilterCollection(array( + $this->getMock('Assetic\\Filter\\FilterInterface'), + $this->getMock('Assetic\\Filter\\FilterInterface'), + )); + + $this->assertInternalType('array', $filter->all(), '->all() returns an array'); + } + + public function testEmptyAll() + { + $filter = new FilterCollection(); + $this->assertInternalType('array', $filter->all(), '->all() returns an array'); + } + + public function testCountable() + { + $filters = new FilterCollection(array($this->getMock('Assetic\\Filter\\FilterInterface'))); + + $this->assertEquals(1, count($filters), 'Countable returns the count'); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/FilterTestCase.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/FilterTestCase.php new file mode 100644 index 00000000..9d6140fb --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/FilterTestCase.php @@ -0,0 +1,50 @@ +assertEquals($expected, $finfo->buffer($data), $message); + } + + protected function findExecutable($name, $serverKey = null) + { + if ($serverKey && isset($_SERVER[$serverKey])) { + return $_SERVER[$serverKey]; + } + + $finder = new ExecutableFinder(); + + return $finder->find($name); + } + + protected function checkNodeModule($module, $bin = null) + { + if (!$bin && !$bin = $this->findExecutable('node', 'NODE_BIN')) { + $this->markTestSkipped('Unable to find `node` executable.'); + } + + $pb = new ProcessBuilder(array($bin, '-e', 'require(\''.$module.'\')')); + + if (isset($_SERVER['NODE_PATH'])) { + $pb->setEnv('NODE_PATH', $_SERVER['NODE_PATH']); + } + + return 0 === $pb->getProcess()->run(); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/GoogleClosure/CompilerApiFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/GoogleClosure/CompilerApiFilterTest.php new file mode 100644 index 00000000..74ff6643 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/GoogleClosure/CompilerApiFilterTest.php @@ -0,0 +1,87 @@ +load(); + + $filter = new CompilerApiFilter(); + $filter->setCompilationLevel(CompilerApiFilter::COMPILE_SIMPLE_OPTIMIZATIONS); + $filter->setJsExterns(''); + $filter->setExternsUrl(''); + $filter->setExcludeDefaultExterns(true); + $filter->setFormatting(CompilerApiFilter::FORMAT_PRETTY_PRINT); + $filter->setUseClosureLibrary(false); + $filter->setWarningLevel(CompilerApiFilter::LEVEL_VERBOSE); + + $filter->filterLoad($asset); + $filter->filterDump($asset); + + $this->assertEquals($expected, $asset->getContent()); + + + $input = <<load(); + + $filter->setLanguage(CompilerApiFilter::LANGUAGE_ECMASCRIPT5); + + $filter->filterLoad($asset); + $filter->filterDump($asset); + + $this->assertEquals($expected, $asset->getContent()); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/GoogleClosure/CompilerJarFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/GoogleClosure/CompilerJarFilterTest.php new file mode 100644 index 00000000..b080f178 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/GoogleClosure/CompilerJarFilterTest.php @@ -0,0 +1,118 @@ +findExecutable('java', 'JAVA_BIN')) { + $this->markTestSkipped('Unable to find `java` executable.'); + } + + if (!isset($_SERVER['CLOSURE_JAR'])) { + $this->markTestSkipped('There is no CLOSURE_JAR environment variable.'); + } + + $this->filter = new CompilerJarFilter($_SERVER['CLOSURE_JAR'], $javaBin); + } + + protected function tearDown() + { + $this->filter = null; + } + + public function testCompile() + { + $input = <<load(); + + $this->filter->filterDump($asset); + + $this->assertEquals($expected, $asset->getContent()); + } + + public function testCompileEcma5() + { + $input = <<load(); + + $this->filter->setLanguage(CompilerJarFilter::LANGUAGE_ECMASCRIPT5); + $this->filter->filterDump($asset); + + $this->assertEquals($expected, $asset->getContent()); + } + + public function testFlagFile() + { + $input = <<load(); + + $this->filter->setFlagFile($flagfile_metadata['uri']); + $this->filter->filterDump($asset); + + fclose($flagfile); + $this->assertEquals($expected, $asset->getContent()); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/GssFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/GssFilterTest.php new file mode 100644 index 00000000..808805f0 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/GssFilterTest.php @@ -0,0 +1,53 @@ +findExecutable('java', 'JAVA_BIN')) { + $this->markTestSkipped('Unable to find `java` executable.'); + } + + if (!isset($_SERVER['GSS_JAR'])) { + $this->markTestSkipped('There is no GSS_JAR environment variable.'); + } + + $this->filter = new GssFilter($_SERVER['GSS_JAR'], $javaBin); + } + + public function testCompile() + { + $input = <<load(); + + $this->filter->filterLoad($asset); + + $this->assertEquals($expected, $asset->getContent()); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/HandlebarsFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/HandlebarsFilterTest.php new file mode 100644 index 00000000..0e2543b1 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/HandlebarsFilterTest.php @@ -0,0 +1,71 @@ +findExecutable('handlebars', 'HANDLEBARS_BIN'); + $nodeBin = $this->findExecutable('node', 'NODE_BIN'); + + if (!$handlebarsBin) { + $this->markTestSkipped('Unable to find `handlebars` executable.'); + } + + $this->asset = new FileAsset(__DIR__.'/fixtures/handlebars/template.handlebars'); + $this->asset->load(); + + $this->filter = new HandlebarsFilter($handlebarsBin, $nodeBin); + } + + protected function tearDown() + { + $this->asset = null; + $this->filter = null; + } + + public function testHandlebars() + { + $this->filter->filterLoad($this->asset); + + $this->assertNotContains('{{ var }}', $this->asset->getContent()); + $this->assertContains('(function() {', $this->asset->getContent()); + } + + public function testSimpleHandlebars() + { + $this->filter->setSimple(true); + $this->filter->filterLoad($this->asset); + + $this->assertNotContains('{{ var }}', $this->asset->getContent()); + $this->assertNotContains('(function() {', $this->asset->getContent()); + } + + public function testMinimizeHandlebars() + { + $this->filter->setMinimize(true); + $this->filter->filterLoad($this->asset); + + $this->assertNotContains('{{ var }}', $this->asset->getContent()); + $this->assertNotContains("\n", $this->asset->getContent()); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/JSMinFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/JSMinFilterTest.php new file mode 100644 index 00000000..09e174e6 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/JSMinFilterTest.php @@ -0,0 +1,39 @@ +markTestSkipped('JSMin is not installed.'); + } + } + + public function testRelativeSourceUrlImportImports() + { + $asset = new FileAsset(__DIR__.'/fixtures/jsmin/js.js'); + $asset->load(); + + $filter = new JSMinFilter(); + $filter->filterDump($asset); + + $this->assertEquals('var a="abc";;;var bbb="u";', $asset->getContent()); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/JSMinPlusFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/JSMinPlusFilterTest.php new file mode 100644 index 00000000..0f8a5f72 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/JSMinPlusFilterTest.php @@ -0,0 +1,39 @@ +markTestSkipped('JSMinPlus is not installed.'); + } + } + + public function testRelativeSourceUrlImportImports() + { + $asset = new FileAsset(__DIR__.'/fixtures/jsmin/js.js'); + $asset->load(); + + $filter = new JSMinPlusFilter(); + $filter->filterDump($asset); + + $this->assertEquals('var a="abc",bbb="u"', $asset->getContent()); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/JSqueezeFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/JSqueezeFilterTest.php new file mode 100644 index 00000000..86419d80 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/JSqueezeFilterTest.php @@ -0,0 +1,39 @@ +markTestSkipped('JSqueeze is not installed.'); + } + } + + public function testRelativeSourceUrlImportImports() + { + $asset = new FileAsset(__DIR__.'/fixtures/jsmin/js.js'); + $asset->load(); + + $filter = new JSqueezeFilter(); + $filter->filterDump($asset); + + $this->assertEquals(";var a='abc',bbb='u';", $asset->getContent()); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/JpegoptimFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/JpegoptimFilterTest.php new file mode 100644 index 00000000..be100802 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/JpegoptimFilterTest.php @@ -0,0 +1,50 @@ +findExecutable('jpegoptim', 'JPEGOPTIM_BIN')) { + $this->markTestSkipped('Unable to find `jpegoptim` executable.'); + } + + $this->filter = new JpegoptimFilter($jpegoptimBin); + } + + protected function tearDown() + { + $this->filter = null; + } + + public function testFilter() + { + $asset = new FileAsset(__DIR__.'/fixtures/home.jpg'); + $asset->load(); + + $before = $asset->getContent(); + $this->filter->filterDump($asset); + + $this->assertNotEmpty($asset->getContent(), '->filterLoad() sets content'); + $this->assertNotEquals($before, $asset->getContent(), '->filterDump() changes the content'); + $this->assertMimeType('image/jpeg', $asset->getContent(), '->filterDump() creates JPEG data'); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/JpegtranFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/JpegtranFilterTest.php new file mode 100644 index 00000000..de7fd668 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/JpegtranFilterTest.php @@ -0,0 +1,50 @@ +findExecutable('jpegtran', 'JPEGTRAN_BIN')) { + $this->markTestSkipped('Unable to find `jpegtran` executable.'); + } + + $this->filter = new JpegtranFilter($jpegtranBin); + } + + protected function tearDown() + { + $this->filter = null; + } + + public function testFilter() + { + $asset = new FileAsset(__DIR__.'/fixtures/home.jpg'); + $asset->load(); + + $before = $asset->getContent(); + $this->filter->filterDump($asset); + + $this->assertNotEmpty($asset->getContent(), '->filterLoad() sets content'); + $this->assertNotEquals($before, $asset->getContent(), '->filterDump() changes the content'); + $this->assertMimeType('image/jpeg', $asset->getContent(), '->filterDump() creates JPEG data'); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/LessFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/LessFilterTest.php new file mode 100644 index 00000000..b33fffe6 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/LessFilterTest.php @@ -0,0 +1,172 @@ +findExecutable('node', 'NODE_BIN')) { + $this->markTestSkipped('Unable to find `node` executable.'); + } + + if (!$this->checkNodeModule('less', $nodeBin)) { + $this->markTestSkipped('The "less" module is not installed.'); + } + + $this->filter = new LessFilter($nodeBin, isset($_SERVER['NODE_PATH']) ? array($_SERVER['NODE_PATH']) : array()); + } + + public function testFilterLoad() + { + $asset = new StringAsset('.foo{.bar{width:(1+1);}}'); + $asset->load(); + + $this->filter->filterLoad($asset); + + $this->assertEquals(".foo .bar {\n width: 2;\n}\n", $asset->getContent(), '->filterLoad() parses the content'); + } + + public function testImport() + { + $expected = <<load(); + + $this->filter->filterLoad($asset); + + $this->assertEquals($expected, $asset->getContent(), '->filterLoad() sets an include path based on source url'); + } + + public function testCompressImport() + { + $expected = <<load(); + + $this->filter->addTreeOption('compress', true); + $this->filter->filterLoad($asset); + + $this->assertEquals($expected, $asset->getContent(), '->filterLoad() sets an include path based on source url'); + } + + public function testLoadPath() + { + $expected = <<filter->addLoadPath(__DIR__.'/fixtures/less'); + + $asset = new StringAsset('@import "main";'); + $asset->load(); + + $this->filter->filterLoad($asset); + + $this->assertEquals($expected, $asset->getContent(), '->filterLoad() adds load paths to include paths'); + } + + public function testSettingLoadPaths() + { + $expected = <<filter->setLoadPaths(array( + __DIR__.'/fixtures/less', + __DIR__.'/fixtures/less/import_path', + )); + + $asset = new StringAsset('@import "main"; @import "_import"; .bar {color: @red}'); + $asset->load(); + + $this->filter->filterLoad($asset); + + $this->assertEquals($expected, $asset->getContent(), '->filterLoad() sets load paths to include paths'); + } + + /** + * @dataProvider provideImports + */ + public function testGetChildren($import) + { + $children = $this->filter->getChildren(new AssetFactory('/'), $import, __DIR__.'/fixtures/less'); + + $this->assertCount(1, $children); + $this->assertEquals('main.less', $children[0]->getSourcePath()); + } + + public function provideImports() + { + return array( + array('@import \'main.less\';'), + array('@import "main.less";'), + array('@import url(\'main.less\');'), + array('@import url("main.less");'), + array('@import url(main.less);'), + array('@import \'main\';'), + array('@import "main";'), + array('@import url(\'main\');'), + array('@import url("main");'), + array('@import url(main);'), + array('@import-once \'main.less\';'), + array('@import-once "main.less";'), + array('@import-once url(\'main.less\');'), + array('@import-once url("main.less");'), + array('@import-once url(main.less);'), + array('@import-once \'main\';'), + array('@import-once "main";'), + array('@import-once url(\'main\');'), + array('@import-once url("main");'), + array('@import-once url(main);'), + ); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/LessphpFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/LessphpFilterTest.php new file mode 100644 index 00000000..941bba13 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/LessphpFilterTest.php @@ -0,0 +1,216 @@ +markTestSkipped('LessPHP is not installed'); + } + + $this->filter = new LessphpFilter(); + } + + /** + * @group integration + */ + public function testFilterLoad() + { + $asset = new StringAsset('.foo{.bar{width:1+1;}}'); + $asset->load(); + + $this->filter->filterLoad($asset); + + $this->assertEquals(".foo .bar {\n width: 2;\n}\n", $asset->getContent(), '->filterLoad() parses the content'); + } + + /** + * @group integration + */ + public function testImport() + { + $expected = <<load(); + + $this->filter->filterLoad($asset); + + $this->assertEquals($expected, $asset->getContent(), '->filterLoad() sets an include path based on source url'); + } + + /** + * @group integration + */ + public function testLoadPath() + { + $expected = <<filter->addLoadPath(__DIR__.'/fixtures/less'); + + $asset = new StringAsset('@import "main";'); + $asset->load(); + + $this->filter->filterLoad($asset); + + $this->assertEquals($expected, $asset->getContent(), '->filterLoad() adds load paths to include paths'); + } + + /** + * @group integration + */ + public function testPresets() + { + $asset = new StringAsset('.foo { color: @bar }'); + $asset->load(); + + $this->filter->setPresets(array('bar' => 'green')); + $this->filter->filterLoad($asset); + + $this->assertContains('green', $asset->getContent(), '->setPresets() to pass variables into lessphp filter'); + } + + /** + * @group integration + */ + public function testFormatterLessjs() + { + $asset = new StringAsset('.foo { color: green; }'); + $asset->load(); + + $this->filter->setFormatter('lessjs'); + $this->filter->filterLoad($asset); + + $this->assertContains("\n color", $asset->getContent(), '->setFormatter("lessjs")'); + } + + /** + * @group integration + */ + public function testFormatterCompressed() + { + $asset = new StringAsset('.foo { color: green; }'); + $asset->load(); + + $this->filter->setFormatter('compressed'); + $this->filter->filterLoad($asset); + + $this->assertContains('color:green', $asset->getContent(), '->setFormatter("compressed")'); + } + + /** + * @group integration + */ + public function testFormatterClassic() + { + $asset = new StringAsset('.foo { color: green; }'); + $asset->load(); + + $this->filter->setFormatter('classic'); + $this->filter->filterLoad($asset); + + $this->assertContains('{ color:green; }', $asset->getContent(), '->setFormatter("classic")'); + } + + /** + * @group integration + */ + public function testPreserveCommentsTrue() + { + $asset = new StringAsset("/* Line 1 */\n.foo { color: green }"); + $asset->load(); + + $this->filter->setPreserveComments(true); + $this->filter->filterLoad($asset); + + $this->assertContains('/* Line 1 */', $asset->getContent(), '->setPreserveComments(true)'); + } + + /** + * @group integration + */ + public function testPreserveCommentsFalse() + { + $asset = new StringAsset("/* Line 1 */\n.foo { color: green }"); + $asset->load(); + + $this->filter->setPreserveComments(false); + $this->filter->filterLoad($asset); + + $this->assertNotContains('/* Line 1 */', $asset->getContent(), '->setPreserveComments(false)'); + } + + /** + * @dataProvider provideImports + */ + public function testGetChildren($import) + { + $children = $this->filter->getChildren(new AssetFactory('/'), $import, __DIR__.'/fixtures/less'); + + $this->assertCount(1, $children); + $this->assertEquals('main.less', $children[0]->getSourcePath()); + } + + public function provideImports() + { + return array( + array('@import \'main.less\';'), + array('@import "main.less";'), + array('@import url(\'main.less\');'), + array('@import url("main.less");'), + array('@import url(main.less);'), + array('@import \'main\';'), + array('@import "main";'), + array('@import url(\'main\');'), + array('@import url("main");'), + array('@import url(main);'), + array('@import-once \'main.less\';'), + array('@import-once "main.less";'), + array('@import-once url(\'main.less\');'), + array('@import-once url("main.less");'), + array('@import-once url(main.less);'), + array('@import-once \'main\';'), + array('@import-once "main";'), + array('@import-once url(\'main\');'), + array('@import-once url("main");'), + array('@import-once url(main);'), + ); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/OptiPngFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/OptiPngFilterTest.php new file mode 100644 index 00000000..f2e96bda --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/OptiPngFilterTest.php @@ -0,0 +1,61 @@ +findExecutable('optipng', 'OPTIPNG_BIN')) { + $this->markTestSkipped('Unable to find `optipng` executable.'); + } + + $this->filter = new OptiPngFilter($optipngBin); + } + + protected function tearDown() + { + $this->filter = null; + } + + /** + * @dataProvider getImages + */ + public function testFilter($image) + { + $asset = new FileAsset($image); + $asset->load(); + + $before = $asset->getContent(); + $this->filter->filterDump($asset); + + $this->assertNotEmpty($asset->getContent(), '->filterDump() sets content'); + $this->assertNotEquals($before, $asset->getContent(), '->filterDump() changes the content'); + $this->assertMimeType('image/png', $asset->getContent(), '->filterDump() creates PNG data'); + } + + public function getImages() + { + return array( + array(__DIR__.'/fixtures/home.gif'), + array(__DIR__.'/fixtures/home.png'), + ); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/PackagerFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/PackagerFilterTest.php new file mode 100644 index 00000000..8e41220b --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/PackagerFilterTest.php @@ -0,0 +1,69 @@ +markTestSkipped('Packager is not available.'); + } + } + + public function testPackager() + { + $expected = <<load(); + + $filter = new PackagerFilter(); + $filter->addPackage(__DIR__.'/fixtures/packager/lib'); + $filter->filterLoad($asset); + + $this->assertEquals($expected, $asset->getContent(), '->filterLoad() runs packager'); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/PackerFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/PackerFilterTest.php new file mode 100644 index 00000000..8c795488 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/PackerFilterTest.php @@ -0,0 +1,39 @@ +markTestSkipped('JavaScriptPacker is not installed.'); + } + } + + public function testPacker() + { + $asset = new FileAsset(__DIR__.'/fixtures/packer/example.js'); + $asset->load(); + + $filter = new PackerFilter(); + $filter->filterDump($asset); + + $this->assertEquals("var exampleFunction=function(arg1,arg2){alert('exampleFunction called!')}", $asset->getContent()); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/PhpCssEmbedFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/PhpCssEmbedFilterTest.php new file mode 100644 index 00000000..e73c6bcb --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/PhpCssEmbedFilterTest.php @@ -0,0 +1,41 @@ +markTestSkipped('PhpCssEmbed is not installed'); + } + } + + public function testCssEmbedDataUri() + { + $data = base64_encode(file_get_contents(__DIR__.'/fixtures/home.png')); + + $asset = new FileAsset(__DIR__ . '/fixtures/cssembed/test.css'); + $asset->load(); + + $filter = new PhpCssEmbedFilter(); + $filter->filterLoad($asset); + + $this->assertContains('url(data:image/png;base64,'.$data, $asset->getContent()); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/PngoutFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/PngoutFilterTest.php new file mode 100644 index 00000000..b787fb6b --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/PngoutFilterTest.php @@ -0,0 +1,62 @@ +findExecutable('pngout', 'PNGOUT_BIN')) { + $this->markTestSkipped('Unable to locate `pngout` executable.'); + } + + $this->filter = new PngoutFilter($pngoutBin); + } + + protected function tearDown() + { + $this->filter = null; + } + + /** + * @dataProvider getImages + */ + public function testFilter($image) + { + $asset = new FileAsset($image); + $asset->load(); + + $before = $asset->getContent(); + $this->filter->filterDump($asset); + + $this->assertNotEmpty($asset->getContent(), '->filterLoad() sets content'); + $this->assertNotEquals($before, $asset->getContent(), '->filterLoad() changes the content'); + $this->assertMimeType('image/png', $asset->getContent(), '->filterLoad() creates PNG data'); + } + + public function getImages() + { + return array( + array(__DIR__.'/fixtures/home.gif'), + array(__DIR__.'/fixtures/home.jpg'), + array(__DIR__.'/fixtures/home.png'), + ); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/RooleFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/RooleFilterTest.php new file mode 100644 index 00000000..197a38e1 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/RooleFilterTest.php @@ -0,0 +1,55 @@ +findExecutable('roole', 'ROOLE_BIN'); + $nodeBin = $this->findExecutable('node', 'NODE_BIN'); + + if (!$rooleBin) { + $this->markTestSkipped('Unable to find `roole` executable.'); + } + + $this->filter = new RooleFilter($rooleBin, $nodeBin); + } + + public function testFilterLoad() + { + $source = <<load(); + + $this->filter->filterLoad($asset); + + $content = $asset->getContent(); + $this->assertNotContains('$margin', $content); + $this->assertContains('margin: 30px;', $content); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/Sass/SassFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/Sass/SassFilterTest.php new file mode 100644 index 00000000..5e9e527b --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/Sass/SassFilterTest.php @@ -0,0 +1,121 @@ +findExecutable('ruby', 'RUBY_BIN'); + if (!$sassBin = $this->findExecutable('sass', 'SASS_BIN')) { + $this->markTestSkipped('Unable to locate `sass` executable.'); + } + + $this->filter = new SassFilter($sassBin, $rubyBin); + } + + protected function tearDown() + { + $this->filter = null; + } + + /** + * @group integration + */ + public function testSass() + { + $input = <<load(); + + $this->filter->setStyle(SassFilter::STYLE_COMPACT); + $this->filter->filterLoad($asset); + + $this->assertEquals("body { color: red; }\n", $asset->getContent(), '->filterLoad() parses the sass'); + } + + /** + * @group integration + */ + public function testScssGuess() + { + $input = <<<'EOF' +$red: #F00; + +.foo { + color: $red; +} + +EOF; + + $expected = '.foo { color: red; }'; + + $asset = new StringAsset($input, array(), null, 'foo.scss'); + $asset->load(); + + $this->filter->setStyle(SassFilter::STYLE_COMPACT); + $this->filter->filterLoad($asset); + + $this->assertEquals(".foo { color: red; }\n", $asset->getContent(), '->filterLoad() detects SCSS based on source path extension'); + } + + public function testGetChildrenCatchesSassImports() + { + $factory = new AssetFactory('/'); // the factory root isn't used + + $children = $this->filter->getChildren($factory, '@import "include";', __DIR__.'/../fixtures/sass'); + $this->assertCount(1, $children); + $this->assertEquals(__DIR__.'/../fixtures/sass', $children[0]->getSourceRoot()); + $this->assertEquals('_include.scss', $children[0]->getSourcePath()); + + $filters = $children[0]->getFilters(); + $this->assertCount(1, $filters); + $this->assertInstanceOf('Assetic\Filter\Sass\SassFilter', $filters[0]); + } + + public function testGetChildrenCatchesPartialsInSubfolders() + { + $factory = new AssetFactory('/'); // the factory root isn't used + + $children = $this->filter->getChildren($factory, '@import "import_path/import";', __DIR__.'/../fixtures/sass'); + $this->assertCount(1, $children); + $this->assertEquals(__DIR__.'/../fixtures/sass', $children[0]->getSourceRoot()); + $this->assertEquals('import_path/_import.scss', $children[0]->getSourcePath()); + } + + public function testGetChildrenIgnoresCssImports() + { + // These aren't ignored yet (todo): + // @import url(main); + // @import "main" screen; + $imports = <<assertEquals(array(), $this->filter->getChildren($factory, $imports, __DIR__.'/../fixtures/sass')); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/Sass/ScssFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/Sass/ScssFilterTest.php new file mode 100644 index 00000000..2fdcaa0f --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/Sass/ScssFilterTest.php @@ -0,0 +1,106 @@ +findExecutable('ruby', 'RUBY_BIN'); + if (!$sassBin = $this->findExecutable('sass', 'SASS_BIN')) { + $this->markTestSkipped('Unable to locate `sass` executable.'); + } + + $this->filter = new ScssFilter($sassBin, $rubyBin); + } + + protected function tearDown() + { + $this->filter = null; + } + + public function testImport() + { + $asset = new FileAsset(__DIR__.'/../fixtures/sass/main.scss'); + $asset->load(); + + $this->filter->setStyle(ScssFilter::STYLE_COMPACT); + $this->filter->filterLoad($asset); + + $expected = <<assertEquals($expected, $asset->getContent(), '->filterLoad() loads imports'); + } + + public function testLoadPath() + { + $expected = <<filter->addLoadPath(__DIR__.'/../fixtures/sass'); + + $asset = new StringAsset('@import "main";'); + $asset->load(); + + $this->filter->filterLoad($asset); + + $this->assertEquals($expected, $asset->getContent(), '->filterLoad() adds load paths to include paths'); + } + + public function testSettingLoadPaths() + { + $expected = <<filter->setLoadPaths(array( + __DIR__.'/../fixtures/sass', + __DIR__.'/../fixtures/sass/import_path', + )); + + $asset = new StringAsset('@import "main"; @import "import"; .bar {color: $red}'); + $asset->load(); + + $this->filter->filterLoad($asset); + + $this->assertEquals($expected, $asset->getContent(), '->filterLoad() sets load paths to include paths'); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/ScssphpFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/ScssphpFilterTest.php new file mode 100644 index 00000000..a629f46f --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/ScssphpFilterTest.php @@ -0,0 +1,131 @@ +markTestSkipped('scssphp is not installed'); + } + } + + public function testFilterLoad() + { + $expected = <<load(); + + $this->getFilter()->filterLoad($asset); + + $this->assertEquals($expected, $asset->getContent(), '->filterLoad() parses the content'); + } + + public function testImport() + { + $expected = <<load(); + + $this->getFilter()->filterLoad($asset); + + $this->assertEquals($expected, $asset->getContent(), '->filterLoad() sets an include path based on source url'); + } + + public function testCompassExtension() + { + $this->markTestIncomplete('Someone fix this, SVP? (Undefined mixin "box-shadow")'); + + $expected = <<load(); + + $this->getFilter(true)->filterLoad($asset); + $this->assertEquals($expected, $asset->getContent(), 'compass plugin can be enabled'); + + $asset = new FileAsset(__DIR__.'/fixtures/sass/main_compass.scss'); + $asset->load(); + + $this->getFilter(false)->filterLoad($asset); + $this->assertEquals("@import \"compass\";\n", $asset->getContent(), 'compass plugin can be disabled'); + } + + public function testSetImportPath() + { + $filter = $this->getFilter(); + $filter->addImportPath(__DIR__.'/fixtures/sass/import_path'); + + $asset = new StringAsset("@import 'import';\n#test { color: \$red }"); + $asset->load(); + $filter->filterLoad($asset); + + $this->assertEquals("#test {\n color: red; }\n", $asset->getContent(), 'Import paths are correctly used'); + } + + + public function testRegisterFunction() + { + $asset = new StringAsset('.foo{ color: bar(); }'); + $asset->load(); + + $filter = $this->getFilter(); + $filter->registerFunction('bar',function(){ return 'red';}); + $filter->filterLoad($asset); + + $expected = new StringAsset('.foo{ color: red;}'); + $expected->load(); + $filter->filterLoad($expected); + + $this->assertEquals($expected->getContent(), $asset->getContent(), 'custom function can be registered'); + } + + // private + + private function getFilter($compass = false) + { + $filter = new ScssphpFilter(); + + if ($compass) { + $filter->enableCompass(); + } + + return $filter; + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/SprocketsFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/SprocketsFilterTest.php new file mode 100644 index 00000000..662b3506 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/SprocketsFilterTest.php @@ -0,0 +1,76 @@ +findExecutable('ruby', 'RUBY_BIN')) { + $this->markTestSkipped('Unable to locate `ruby` executable.'); + } + + if (!isset($_SERVER['SPROCKETS_LIB'])) { + $this->markTestSkipped('There is no SPROCKETS_LIB environment variable.'); + } + + $this->filter = new SprocketsFilter($_SERVER['SPROCKETS_LIB'], $rubyBin); + + $this->assetRoot = sys_get_temp_dir().'/assetic_sprockets'; + if (is_dir($this->assetRoot)) { + $this->cleanup(); + } else { + mkdir($this->assetRoot); + } + } + + protected function tearDown() + { + $this->filter = null; + $this->cleanup(); + } + + public function testFilterLoad() + { + $asset = new FileAsset(__DIR__.'/fixtures/sprockets/main.js'); + $asset->load(); + + $this->filter->addIncludeDir(__DIR__.'/fixtures/sprockets/lib1'); + $this->filter->addIncludeDir(__DIR__.'/fixtures/sprockets/lib2'); + $this->filter->setAssetRoot($this->assetRoot); + $this->filter->filterLoad($asset); + + $this->assertContains('/* header.js */', $asset->getContent()); + $this->assertContains('/* include.js */', $asset->getContent()); + $this->assertContains('/* footer.js */', $asset->getContent()); + $this->assertFileExists($this->assetRoot.'/images/image.gif'); + } + + private function cleanup() + { + $it = new \RecursiveDirectoryIterator($this->assetRoot); + foreach (new \RecursiveIteratorIterator($it) as $path => $file) { + if (is_file($path)) { + unlink($path); + } + } + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/StylusFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/StylusFilterTest.php new file mode 100644 index 00000000..1c59e488 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/StylusFilterTest.php @@ -0,0 +1,68 @@ +findExecutable('node', 'NODE_BIN')) { + $this->markTestSkipped('Unable to find `node` executable.'); + } + + if (!$this->checkNodeModule('stylus', $nodeBin)) { + $this->markTestSkipped('The "stylus" module is not installed.'); + } + + $this->filter = new StylusFilter($nodeBin, isset($_SERVER['NODE_PATH']) ? array($_SERVER['NODE_PATH']) : array()); + } + + public function testFilterLoad() + { + $asset = new StringAsset("body\n font 12px Helvetica, Arial, sans-serif\n color black"); + $asset->load(); + + $this->filter->filterLoad($asset); + + $this->assertEquals("body {\n font: 12px Helvetica, Arial, sans-serif;\n color: #000;\n}\n", $asset->getContent(), '->filterLoad() parses the content'); + } + + public function testFilterLoadWithCompression() + { + $asset = new StringAsset("body\n font 12px Helvetica, Arial, sans-serif\n color black;"); + $asset->load(); + + $this->filter->setCompress(true); + $this->filter->filterLoad($asset); + + $this->assertEquals("body{font:12px Helvetica,Arial,sans-serif;color:#000}\n", $asset->getContent(), '->filterLoad() parses the content and compress it'); + } + + public function testFilterLoadWithUseNib() + { + $asset = new StringAsset("@import 'nib'\nbody\n whitespace nowrap\n font 12px Helvetica, Arial, sans-serif\n color black"); + $asset->load(); + + $this->filter->setUseNib(true); + $this->filter->filterLoad($asset); + + $this->assertEquals("body {\n white-space: nowrap;\n font: 12px Helvetica, Arial, sans-serif;\n color: #000;\n}\n", $asset->getContent(), '->filterLoad() parses the content using the nib extension'); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/TypeScriptFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/TypeScriptFilterTest.php new file mode 100644 index 00000000..80f291cb --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/TypeScriptFilterTest.php @@ -0,0 +1,70 @@ +findExecutable('tsc', 'TSC_BIN'); + $nodeBin = $this->findExecutable('node', 'NODE_BIN'); + + if (!$tscBin) { + $this->markTestSkipped('Unable to find `tsc` executable.'); + } + + $this->filter = new TypeScriptFilter($tscBin, $nodeBin); + } + + protected function tearDown() + { + unset($this->filter); + } + + public function testFilterLoad() + { + $typescript = <<load(); + + $this->filter->filterLoad($asset); + + $this->assertContains('function greeter(person)', $asset->getContent()); + $this->assertNotContains('interface Person', $asset->getContent()); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/UglifyCssFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/UglifyCssFilterTest.php new file mode 100644 index 00000000..48750f71 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/UglifyCssFilterTest.php @@ -0,0 +1,55 @@ +findExecutable('uglifycss', 'UGLIFYCSS_BIN'); + $nodeBin = $this->findExecutable('node', 'NODE_BIN'); + if (!$uglifycssBin) { + $this->markTestSkipped('Unable to find `uglifycss` executable.'); + } + + $this->asset = new FileAsset(__DIR__.'/fixtures/uglifycss/main.css'); + $this->asset->load(); + + $this->filter = new UglifyCssFilter($uglifycssBin, $nodeBin); + } + + protected function tearDown() + { + $this->asset = null; + $this->filter = null; + } + + public function testUglify() + { + $this->filter->filterDump($this->asset); + + $expected = <<assertSame($expected, $this->asset->getContent()); + } + +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/UglifyJs2FilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/UglifyJs2FilterTest.php new file mode 100644 index 00000000..e021c142 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/UglifyJs2FilterTest.php @@ -0,0 +1,101 @@ +findExecutable('uglifyjs', 'UGLIFYJS2_BIN'); + $nodeBin = $this->findExecutable('node', 'NODE_BIN'); + if (!$uglifyjsBin) { + $this->markTestSkipped('Unable to find `uglifyjs` executable.'); + } + + // verify uglifyjs version + $pb = new ProcessBuilder($nodeBin ? array($nodeBin, $uglifyjsBin) : array($uglifyjsBin)); + $pb->add('--version'); + if (isset($_SERVER['NODE_PATH'])) { + $pb->setEnv('NODE_PATH', $_SERVER['NODE_PATH']); + } + if (0 !== $pb->getProcess()->run()) { + $this->markTestSkipped('Incorrect version of UglifyJs'); + } + + $this->asset = new FileAsset(__DIR__.'/fixtures/uglifyjs/script.js'); + $this->asset->load(); + + $this->filter = new UglifyJs2Filter($uglifyjsBin, $nodeBin); + } + + protected function tearDown() + { + $this->asset = null; + $this->filter = null; + } + + public function testUglify() + { + $this->filter->filterDump($this->asset); + + $this->assertContains('function', $this->asset->getContent()); + $this->assertNotContains('/**', $this->asset->getContent()); + } + + public function testCompress() + { + $this->filter->setCompress(true); + $this->filter->filterDump($this->asset); + + $this->assertContains('var foo', $this->asset->getContent()); + $this->assertNotContains('var var1', $this->asset->getContent()); + } + + public function testMangle() + { + $this->filter->setMangle(true); + $this->filter->filterDump($this->asset); + + $this->assertContains('new Array(1,2,3,4)', $this->asset->getContent()); + $this->assertNotContains('var var2', $this->asset->getContent()); + } + + public function testCompressAndMangle() + { + $this->filter->setCompress(true); + $this->filter->setMangle(true); + $this->filter->filterDump($this->asset); + + $this->assertNotContains('var var1', $this->asset->getContent()); + $this->assertNotContains('var var2', $this->asset->getContent()); + $this->assertContains('new Array(1,2,3,4)', $this->asset->getContent()); + } + + public function testBeautify() + { + $this->filter->setBeautify(true); + $this->filter->filterDump($this->asset); + + $this->assertContains(' foo', $this->asset->getContent()); + $this->assertNotContains('/**', $this->asset->getContent()); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/UglifyJsFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/UglifyJsFilterTest.php new file mode 100644 index 00000000..e1e67884 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/UglifyJsFilterTest.php @@ -0,0 +1,125 @@ +findExecutable('uglifyjs', 'UGLIFYJS_BIN'); + $nodeBin = $this->findExecutable('node', 'NODE_BIN'); + if (!$uglifyjsBin) { + $this->markTestSkipped('Unable to find `uglifyjs` executable.'); + } + + // verify uglifyjs version + $pb = new ProcessBuilder($nodeBin ? array($nodeBin, $uglifyjsBin) : array($uglifyjsBin)); + $pb->add('--version'); + if (isset($_SERVER['NODE_PATH'])) { + $pb->setEnv('NODE_PATH', $_SERVER['NODE_PATH']); + } + if (0 === $pb->getProcess()->run()) { + $this->markTestSkipped('Incorrect version of UglifyJs'); + } + + $this->asset = new FileAsset(__DIR__.'/fixtures/uglifyjs/script.js'); + $this->asset->load(); + + $this->filter = new UglifyJsFilter($uglifyjsBin, $nodeBin); + } + + protected function tearDown() + { + $this->asset = null; + $this->filter = null; + } + + public function testUglify() + { + $this->filter->filterDump($this->asset); + + $expected = <<assertEquals($expected, $this->asset->getContent()); + } + + public function testUnsafeUglify() + { + $this->filter->setUnsafe(true); + $this->filter->filterDump($this->asset); + + $expected = <<assertEquals($expected, $this->asset->getContent()); + } + + public function testBeautifyUglify() + { + $this->filter->setBeautify(true); + $this->filter->filterDump($this->asset); + + $expected = <<assertEquals($expected, $this->asset->getContent()); + } + + public function testNoMangleUglify() + { + $this->filter->setMangle(false); + $this->filter->filterDump($this->asset); + + $expected = <<assertEquals($expected, $this->asset->getContent()); + } + + public function testNoCopyrightUglify() + { + $this->filter->setNoCopyright(true); + $this->filter->filterDump($this->asset); + + $expected = '(function(){function t(e){return r.push(e),e}var e=new Array(1,2,3,4),t=Array(a,b,c),n=new Array(5),r=new Array(a),e=function(e){return e};e("abc123"),t("abc123")})();'; + $this->assertEquals($expected, $this->asset->getContent()); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/Yui/BaseCompressorFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/Yui/BaseCompressorFilterTest.php new file mode 100644 index 00000000..a85e9723 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/Yui/BaseCompressorFilterTest.php @@ -0,0 +1,31 @@ +assertInstanceOf('Assetic\\Filter\\FilterInterface', $filter, 'BaseCompressorFilter implements FilterInterface'); + } +} + +class YuiCompressorFilterForTest extends BaseCompressorFilter +{ + public function filterDump(AssetInterface $asset) + { + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/Yui/CssCompressorFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/Yui/CssCompressorFilterTest.php new file mode 100644 index 00000000..9a978cd6 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/Yui/CssCompressorFilterTest.php @@ -0,0 +1,23 @@ +assertInstanceOf('Assetic\\Filter\\FilterInterface', $filter, 'CssCompressorFilter implements FilterInterface'); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/Yui/JsCompressorFilterTest.php b/kriswallsmith/assetic/tests/Assetic/Test/Filter/Yui/JsCompressorFilterTest.php new file mode 100644 index 00000000..7f4ac620 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/Yui/JsCompressorFilterTest.php @@ -0,0 +1,63 @@ +assertInstanceOf('Assetic\\Filter\\FilterInterface', $filter, 'JsCompressorFilter implements FilterInterface'); + } + + /** + * @group integration + */ + public function testFilterDump() + { + if (!isset($_SERVER['YUI_COMPRESSOR_JAR'])) { + $this->markTestSkipped('There is no YUI_COMPRESSOR_JAR environment variable.'); + } + + $source = <<load(); + + $filter = new JsCompressorFilter($_SERVER['YUI_COMPRESSOR_JAR']); + $filter->filterDump($asset); + + $this->assertEquals($expected, $asset->getContent(), '->filterDump()'); + } +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/compass/compass.sass b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/compass/compass.sass new file mode 100644 index 00000000..dfc99fbd --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/compass/compass.sass @@ -0,0 +1,4 @@ +@import "compass/typography/links/hover-link" + +a + @include hover-link diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/compass/partials/_sass.sass b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/compass/partials/_sass.sass new file mode 100644 index 00000000..8593aabd --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/compass/partials/_sass.sass @@ -0,0 +1,4 @@ +@import "compass/utilities" + +@mixin mixin-test($fontSize: 1em) + font-size: $fontSize \ No newline at end of file diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/compass/partials/_scss.scss b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/compass/partials/_scss.scss new file mode 100644 index 00000000..896ba5b6 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/compass/partials/_scss.scss @@ -0,0 +1,6 @@ +@import "compass/utilities"; + +@mixin mixin-test($fontSize: 1em) +{ + font-size: $fontSize; +} \ No newline at end of file diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/compass/stylesheet.sass b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/compass/stylesheet.sass new file mode 100644 index 00000000..569d84eb --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/compass/stylesheet.sass @@ -0,0 +1,4 @@ +@import "partials/sass" + +.test-class + @include mixin-test(2em) \ No newline at end of file diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/compass/stylesheet.scss b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/compass/stylesheet.scss new file mode 100644 index 00000000..461884c9 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/compass/stylesheet.scss @@ -0,0 +1,6 @@ +@import "partials/scss"; + +.test-class +{ + @include mixin-test(2em); +} \ No newline at end of file diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssembed/test.css b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssembed/test.css new file mode 100644 index 00000000..370982ce --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssembed/test.css @@ -0,0 +1,4 @@ +.test +{ + background: url(../home.png); +} \ No newline at end of file diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssimport/import.css b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssimport/import.css new file mode 100644 index 00000000..e2c77fee --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssimport/import.css @@ -0,0 +1,2 @@ +/* import.css */ +body { color: red; } \ No newline at end of file diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssimport/main.css b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssimport/main.css new file mode 100644 index 00000000..1a90d49a --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssimport/main.css @@ -0,0 +1,4 @@ +/* main.css */ +@import "import.css"; +@import url('more/evenmore/deep1.css'); +body { color: black; } \ No newline at end of file diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssimport/more.sass b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssimport/more.sass new file mode 100644 index 00000000..1463b168 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssimport/more.sass @@ -0,0 +1 @@ +/* more.sass */ \ No newline at end of file diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssimport/more/evenmore/bg.gif b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssimport/more/evenmore/bg.gif new file mode 100644 index 00000000..e69de29b diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssimport/more/evenmore/deep1.css b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssimport/more/evenmore/deep1.css new file mode 100644 index 00000000..433b3415 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssimport/more/evenmore/deep1.css @@ -0,0 +1,2 @@ +/* more/evenmore/deep1.css */ +@import url(deep2.css); \ No newline at end of file diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssimport/more/evenmore/deep2.css b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssimport/more/evenmore/deep2.css new file mode 100644 index 00000000..645a40eb --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssimport/more/evenmore/deep2.css @@ -0,0 +1,4 @@ +/* more/evenmore/deep2.css */ +body { + background: url(bg.gif); +} \ No newline at end of file diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssimport/noncssimport.css b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssimport/noncssimport.css new file mode 100644 index 00000000..2cea30f0 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssimport/noncssimport.css @@ -0,0 +1,2 @@ +/* noncssimport.css */ +@import "more.sass"; \ No newline at end of file diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssmin/fonts.css b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssmin/fonts.css new file mode 100644 index 00000000..2d135f60 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssmin/fonts.css @@ -0,0 +1,3 @@ +body { + color: white; +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssmin/main.css b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssmin/main.css new file mode 100644 index 00000000..8fbabc82 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/cssmin/main.css @@ -0,0 +1,5 @@ +@import url("fonts.css"); + +body { + background: black; +} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/handlebars/template.handlebars b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/handlebars/template.handlebars new file mode 100644 index 00000000..ae9839ed --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/handlebars/template.handlebars @@ -0,0 +1 @@ +

{{ var }}

\ No newline at end of file diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/home.gif b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/home.gif new file mode 100644 index 00000000..edda5603 Binary files /dev/null and b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/home.gif differ diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/home.jpg b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/home.jpg new file mode 100644 index 00000000..e0ad369c Binary files /dev/null and b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/home.jpg differ diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/home.png b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/home.png new file mode 100644 index 00000000..bf12e0b9 Binary files /dev/null and b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/home.png differ diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/jsmin/js.js b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/jsmin/js.js new file mode 100644 index 00000000..76b74315 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/jsmin/js.js @@ -0,0 +1,7 @@ +var a = "abc"; + +// fsfafwe + +;; + var bbb = "u"; + \ No newline at end of file diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/less/_include.less b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/less/_include.less new file mode 100644 index 00000000..8e508d85 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/less/_include.less @@ -0,0 +1 @@ +.foo { color: blue; } diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/less/import_path/_import.less b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/less/import_path/_import.less new file mode 100644 index 00000000..7a5d1d4d --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/less/import_path/_import.less @@ -0,0 +1 @@ +@red: #ff0000; diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/less/main.less b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/less/main.less new file mode 100644 index 00000000..86caa7b1 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/less/main.less @@ -0,0 +1,3 @@ +@import "_include"; + +.foo { color: red; } diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/packager/app/application.js b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/packager/app/application.js new file mode 100644 index 00000000..c0a775c3 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/packager/app/application.js @@ -0,0 +1,11 @@ +/* +--- + +name: App + +requires: [Util/Util] + +... +*/ + +var bar = foo(); diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/packager/lib/package.yml b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/packager/lib/package.yml new file mode 100644 index 00000000..331b341f --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/packager/lib/package.yml @@ -0,0 +1,4 @@ +name: "Util" + +sources: + - "util.js" diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/packager/lib/util.js b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/packager/lib/util.js new file mode 100644 index 00000000..bb94a5a4 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/packager/lib/util.js @@ -0,0 +1,11 @@ +/* +--- + +name: Util + +provides: [Util] + +... +*/ + +function foo() {} diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/packer/example.js b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/packer/example.js new file mode 100644 index 00000000..d80ab5ad --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/packer/example.js @@ -0,0 +1,7 @@ +/** + * Example function + */ +var exampleFunction = function(arg1, arg2) { + // Some comment... + alert('exampleFunction called!'); +} \ No newline at end of file diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sass/_include.scss b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sass/_include.scss new file mode 100644 index 00000000..8e508d85 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sass/_include.scss @@ -0,0 +1 @@ +.foo { color: blue; } diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sass/import_path/_import.scss b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sass/import_path/_import.scss new file mode 100644 index 00000000..856101d9 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sass/import_path/_import.scss @@ -0,0 +1 @@ +$red: red; \ No newline at end of file diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sass/main.scss b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sass/main.scss new file mode 100644 index 00000000..b9cbf42c --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sass/main.scss @@ -0,0 +1,3 @@ +@import "include"; + +.foo { color: red; } diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sass/main_compass.scss b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sass/main_compass.scss new file mode 100644 index 00000000..8f0df0f6 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sass/main_compass.scss @@ -0,0 +1,5 @@ +@import "compass"; + +.shadow { + @include box-shadow(10px 10px 8px red); +} \ No newline at end of file diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sprockets/include.js b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sprockets/include.js new file mode 100644 index 00000000..a17b90b2 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sprockets/include.js @@ -0,0 +1 @@ +/* include.js */ diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sprockets/lib1/assets/images/image.gif b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sprockets/lib1/assets/images/image.gif new file mode 100644 index 00000000..f32722af Binary files /dev/null and b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sprockets/lib1/assets/images/image.gif differ diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sprockets/lib1/header.js b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sprockets/lib1/header.js new file mode 100644 index 00000000..c60f8d54 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sprockets/lib1/header.js @@ -0,0 +1,3 @@ +/* header.js */ + +//= provide "assets" diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sprockets/lib2/footer.js b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sprockets/lib2/footer.js new file mode 100644 index 00000000..5ce2b9c4 --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sprockets/lib2/footer.js @@ -0,0 +1 @@ +/* footer.js */ diff --git a/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sprockets/main.js b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sprockets/main.js new file mode 100644 index 00000000..6ea483ac --- /dev/null +++ b/kriswallsmith/assetic/tests/Assetic/Test/Filter/fixtures/sprockets/main.js @@ -0,0 +1,5 @@ +//= require
+ +//= require "include" + +//= require