Browse files

Added IO interface for alternative methods of cache management

Updated Prepend.css and initial-values.ini
Internal refactoring
  • Loading branch information...
1 parent 0084fed commit 3841bba6045dee7f96cc2990f4842bc0a8521c54 @peteboere committed Apr 11, 2012
View
5 CHANGELOG
@@ -1,3 +1,8 @@
+1.4.3
+-----
+
+
+
1.4.2
-----
Fixed bug with @import statement parsing
View
4 CssCrush.php
@@ -4,7 +4,7 @@
* CSS Crush
* Extensible CSS preprocessor
*
- * @version 1.4.2
+ * @version 1.4.3
* @license http://www.opensource.org/licenses/mit-license.php (MIT)
* @copyright Copyright 2010-2012 Pete Boere
*
@@ -22,6 +22,7 @@
*/
require_once 'lib/Util.php';
+require_once 'lib/IO.php';
require_once 'lib/Core.php';
CssCrush::init( dirname( __FILE__ ) );
@@ -31,6 +32,7 @@
CssCrush_Function::init();
require_once 'lib/Importer.php';
+require_once 'lib/Iterator.php';
require_once 'lib/Color.php';
require_once 'lib/Hook.php';
View
6 Plugins.ini
@@ -30,4 +30,8 @@ plugins[] = double-colon.php
plugins[] = hocus-pocus.php
; CSS3 'initial' keyword shim
-plugins[] = initial.php
+plugins[] = initial.php
+
+; Transforms correctly-spelt Queen's English into valid CSS (http://spiffingcss.com)
+; plugins[] = spiffing.php
+
View
43 Prepend.css
@@ -1,36 +1,35 @@
/*$!
-Prepend.css contains library variables by default, but it could also contain reset styles such as reset.css or normalize.css that you would need prepended to every css file.
+Prepend.css contains library variables by default, but it could also contain reset styles such as reset.css or normalize.css that you would want prepended to every output file.
*/
@define {
- /*------------------------
- Font stacks
- ------------------------*/
+ /* Font stacks
+ ---------------------------------------- */
/* Serif */
- georgia: Georgia, Times, "Times New Roman", serif;
- times: Times, "Times New Roman", serif;
- palatino: Palatino, 'Palatino Linotype', "Hoefler Text", serif;
- serif: $( times );
+ baskerville: Baskerville, Times, "Times New Roman", serif;
+ georgia: Georgia, Times, "Times New Roman", serif;
+ palatino: Palatino, "Palatino Linotype", "Hoefler Text", serif;
+ times: Times, "Times New Roman", serif;
+ serif: $( times );
/* Sans-serif */
- helvetica: "Helvetica Neue", Helvetica, Arial, sans-serif;
- arial: "Arial Unicode MS", Arial, Helvetica, sans-serif;
- verdana: Verdana, Tahoma, Arial, sans-serif;
- lucida: "Lucida Sans Unicode", "Lucida Sans", "Lucida Grande", Verdana, sans-serif;
- sans-serif: $( arial );
-
- /* Monospace */
- courier: "Courier New", Courier, mono;
- consolas: Consolas, "Lucida Console", Monaco, "Courier New", Courier, mono;
- monaco: Monaco, "Courier New", Courier, mono;
- mono: $( courier );
-
- /* Unicode */
- unicode: "Arial Unicode MS", Arial, "Microsoft Sans Serif", "Lucida Grande", sans-serif;
+ arial: Arial, "Arial Unicode MS", Helvetica, sans-serif;
+ gill-sans: "Gill Sans", Calibri, "Trebuchet MS", sans-serif;
+ helvetica: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ lucida: "Lucida Sans Unicode", "Lucida Sans", "Lucida Grande", Verdana, sans-serif;
+ trebuchet-ms: "Trebuchet MS", "Lucida Sans Unicode", "Lucida Grande", "Lucida Sans", Arial, sans-serif;
+ verdana: Verdana, Tahoma, Arial, sans-serif;
+ sans-serif: $( arial );
+
+ /* Monospace */
+ consolas: Consolas, "Lucida Console", Monaco, "Courier New", Courier, monospace;
+ courier: "Courier New", Courier, monospace;
+ monaco: Monaco, "Courier New", Courier, monospace;
+ monospace: $( courier );
}
View
26 cli.php
@@ -7,8 +7,14 @@
require_once 'CssCrush.php';
+
+// Exit status constants
+define( 'STATUS_OK', 0 );
+define( 'STATUS_ERROR', 1 );
+
// Get stdin input
$stdin = fopen( 'php://stdin', 'r' );
+stream_set_blocking( $stdin, false ) or die ( 'Failed to disable stdin blocking' );
$stdin_contents = stream_get_contents( $stdin );
fclose( $stdin );
@@ -23,7 +29,7 @@
$required_version = 5.3;
if ( $version < $required_version ) {
fwrite( $stdout, "PHP version $required_version or higher is required to use this tool.\nYou are currently running PHP version $version\n\n" );
- exit( 1 );
+ exit( STATUS_ERROR );
}
@@ -107,7 +113,7 @@
if ( $help_flag ) {
fwrite( $stdout, $help );
- exit( 1 );
+ exit( STATUS_OK );
}
@@ -118,8 +124,8 @@
if ( $input_file ) {
if ( ! file_exists( $input_file ) ) {
- fwrite( $stdout, "can't find input file\n\n" );
- exit( 0 );
+ fwrite( $stdout, "Input file not found\n\n" );
+ exit( STATUS_ERROR );
}
$input = file_get_contents( $input_file );
}
@@ -128,7 +134,7 @@
}
else {
fwrite( $stdout, $help );
- exit( 1 );
+ exit( STATUS_OK );
}
@@ -170,18 +176,20 @@
if ( $output_file ) {
if ( ! @file_put_contents( $output_file, $output ) ) {
- fwrite( $stdout, "Could not write to path '$output_file'\n" );
+ $message = "Could not write to path '$output_file'\n";
if ( strpos( $output_file, '~' ) === 0 ) {
- fwrite( $stdout, "No tilde expansion\n" );
+ $message .= "Tilde expansion does not work\n";
}
- exit( 0 );
+ fwrite( $stdout, $message );
+ exit( STATUS_ERROR );
}
}
else {
$output .= "\n";
fwrite( $stdout, $output );
}
-exit( 1 );
+
+exit( STATUS_OK );
View
5 lib/Color.php
@@ -12,7 +12,7 @@ class csscrush_color {
public static function getKeywords () {
// Load the keywords if necessary
if ( empty( self::$keywords ) ) {
- $path = csscrush::$location . '/misc/color-keywords.ini';
+ $path = csscrush::$config->location . '/misc/color-keywords.ini';
if ( $keywords = parse_ini_file( $path ) ) {
foreach ( $keywords as $word => $rgb ) {
$rgb = array_map( 'intval', explode( ',', $rgb ) );
@@ -162,4 +162,5 @@ public static function hexToRgb ( $hex ) {
return array_map( 'hexdec', $hex );
}
-}
+}
+
View
502 lib/Core.php
@@ -10,27 +10,13 @@ class csscrush {
// Path information, global settings
public static $config;
- // The path of this script
- public static $location;
-
- // Aliases from the aliases file
- public static $aliases = array();
- public static $aliasesRaw = array();
-
- // Macro function names
- public static $macros = array();
-
- public static $COMPILE_SUFFIX = '.crush.css';
-
- // Global variable storage
- protected static $globalVars = array();
+ // Properties available to each process
+ public static $process;
+ public static $options;
+ public static $storage;
+ // Internal
protected static $assetsLoaded = false;
-
- // Properties available to each 'file' process
- public static $storage;
- public static $compileName;
- public static $options;
protected static $tokenUID;
// Regular expressions
@@ -75,34 +61,54 @@ class csscrush {
// Init called once manually post class definition
public static function init ( $current_dir ) {
- self::$location = $current_dir;
+ self::$config = new stdclass();
- self::$config = $config = new stdClass;
- $config->file = '.' . __CLASS__;
- $config->data = null;
- $config->path = null;
- $config->baseDir = null;
- $config->baseURL = null;
+ // Path to this installation
+ self::$config->location = $current_dir;
// Get normalized document root reference: no symlink, forward slashes, no trailing slashes
- $docRoot = null;
+ $doc_root = null;
if ( isset( $_SERVER[ 'DOCUMENT_ROOT' ] ) ) {
- $docRoot = realpath( $_SERVER[ 'DOCUMENT_ROOT' ] );
+ $doc_root = realpath( $_SERVER[ 'DOCUMENT_ROOT' ] );
}
else {
// Probably IIS
$scriptname = $_SERVER[ 'SCRIPT_NAME' ];
$fullpath = realpath( basename( $scriptname ) );
- $docRoot = substr( $fullpath, 0, stripos( $fullpath, $scriptname ) );
+ $doc_root = substr( $fullpath, 0, stripos( $fullpath, $scriptname ) );
}
- $config->docRoot = csscrush_util::normalizeSystemPath( $docRoot );
+ self::$config->docRoot = csscrush_util::normalizeSystemPath( $doc_root );
+
+ // Set the default IO handler
+ self::$config->io = 'csscrush_io';
+
+ // Global assets
+ self::$config->vars = array();
+ self::$config->plugins = array();
+ self::$config->aliases = array();
+ self::$config->aliasesRaw = array();
// Casting to objects for ease of use
self::$regex = (object) self::$regex;
self::$regex->token = (object) self::$regex->token;
self::$regex->function = (object) self::$regex->function;
}
+
+ public static function io_call ( $method ) {
+
+ // Fetch the argument list, shift off the first item
+ $args = func_get_args();
+ array_shift( $args );
+
+ // The method address
+ $the_method = array( self::$config->io, $method );
+
+ // Return the call result
+ return call_user_func_array( $the_method, $args );
+ }
+
+
// Aliases and macros loader
protected static function loadAssets () {
@@ -113,16 +119,16 @@ protected static function loadAssets () {
// Load aliases file if it exists
if ( $aliases_file ) {
if ( $result = @parse_ini_file( $aliases_file, true ) ) {
- self::$aliasesRaw = $result;
+ self::$config->aliasesRaw = $result;
// Value aliases require a little preprocessing
- if ( isset( self::$aliasesRaw[ 'values' ] ) ) {
+ if ( isset( self::$config->aliasesRaw[ 'values' ] ) ) {
$store = array();
- foreach ( self::$aliasesRaw[ 'values' ] as $prop_val => $aliases ) {
+ foreach ( self::$config->aliasesRaw[ 'values' ] as $prop_val => $aliases ) {
list( $prop, $value ) = array_map( 'trim', explode( ':', $prop_val ) );
$store[ $prop ][ $value ] = $aliases;
}
- self::$aliasesRaw[ 'values' ] = $store;
+ self::$config->aliasesRaw[ 'values' ] = $store;
}
}
else {
@@ -141,8 +147,9 @@ protected static function loadAssets () {
if ( $plugins_file ) {
if ( $result = @parse_ini_file( $plugins_file ) ) {
foreach ( $result[ 'plugins' ] as $plugin_file ) {
- $path = self::$location . "/plugins/$plugin_file";
+ $path = self::$config->location . "/plugins/$plugin_file";
if ( file_exists( $path ) ) {
+ self::$config->plugins[] = $plugin_file;
require_once $path;
}
else {
@@ -156,70 +163,32 @@ protected static function loadAssets () {
}
}
- // Initialize config data, create config cache file if needed
- protected static function loadCacheData () {
- $config = self::$config;
- if (
- file_exists( $config->path ) and
- $config->data and
- $config->data[ 'originPath' ] == $config->path
- ) {
- // Already loaded and config file exists in the current directory
- return;
- }
-
- $configFileExists = file_exists( $config->path );
- $configFileWritable = $configFileExists ? is_writable( $config->path ) : false;
- if ( $configFileExists and $configFileWritable ) {
- // Load from file
- $config->data = unserialize( file_get_contents( $config->path ) );
- }
- else {
- // Config file may exist but not be writable (may not be visible in some ftp situations?)
- if ( $configFileExists ) {
- if ( ! @unlink( $config->path ) ) {
- trigger_error( __METHOD__ . ": Could not delete config data file.\n", E_USER_NOTICE );
- }
- }
- // Create
- self::log( 'Creating config data file' );
- file_put_contents( $config->path, serialize( array() ) );
- $config->data = array();
- }
- }
-
- // Establish the hostfile directory and optionally test it's writable
- protected static function setPath ( $new_dir, $write_test = true ) {
+ // Establish the input and output directories and optionally test if output dir writable
+ protected static function setPath ( $input_dir, $write_test = true ) {
$config = self::$config;
- $docRoot = $config->docRoot;
+ $process = self::$process;
+ $doc_root = $config->docRoot;
- if ( strpos( $new_dir, $docRoot ) !== 0 ) {
+ if ( strpos( $input_dir, $doc_root ) !== 0 ) {
// Not a system path
- $new_dir = realpath( "$docRoot/$new_dir" );
+ $input_dir = realpath( "$doc_root/$input_dir" );
}
- $pathtest = true;
- if ( ! file_exists( $new_dir ) ) {
- trigger_error( __METHOD__ . ": directory '$new_dir' doesn't exist.\n", E_USER_WARNING );
- $pathtest = false;
- }
- else if ( $write_test and ! is_writable( $new_dir ) ) {
- self::log( 'Attempting to change permissions' );
- if ( ! @chmod( $new_dir, 0755 ) ) {
- trigger_error( __METHOD__ . ": directory '$new_dir' is unwritable.\n", E_USER_WARNING );
- self::log( 'Unable to update permissions' );
- $pathtest = false;
- }
- else {
- self::log( 'Permissions updated' );
- }
- }
+ // Store input directory
+ $process->inputDir = $input_dir;
+ $process->inputDirUrl = substr( $process->inputDir, strlen( $doc_root ) );
+
+ // Store reference to the output dir
+ $process->outputDir = csscrush::io_call( 'getOutputDir' );
+ $process->outputDirUrl = substr( $process->outputDir, strlen( $doc_root ) );
- $config->path = "$new_dir/$config->file";
- $config->baseDir = $new_dir;
- $config->baseURL = substr( $new_dir, strlen( $docRoot ) );
+ // Test the output directory to see if it's writable
+ $pathtest = csscrush::io_call( 'testOutputDir', $write_test );
+
+ // Setup the IO handler
+ csscrush::io_call( 'init' );
return $pathtest;
}
@@ -237,23 +206,26 @@ protected static function setPath ( $new_dir, $write_test = true ) {
*/
public static function file ( $file, $options = null ) {
- $config = self::$config;
-
// Reset for current process
- self::reset();
+ self::reset( $options );
+
+ $config = self::$config;
+ $process = self::$process;
+ $options = self::$options;
+ $doc_root = $config->docRoot;
// Since we're comparing strings, we need to iron out OS differences
$file = str_replace( '\\', '/', $file );
- $docRoot = $config->docRoot;
-
+
+ // Finding the system path of the input file and validating it
$pathtest = true;
- if ( strpos( $file, $docRoot ) === 0 ) {
+ if ( strpos( $file, $doc_root ) === 0 ) {
// System path
$pathtest = self::setPath( dirname( $file ) );
}
else if ( strpos( $file, '/' ) === 0 ) {
// WWW root path
- $pathtest = self::setPath( dirname( $docRoot . $file ) );
+ $pathtest = self::setPath( dirname( $doc_root . $file ) );
}
else {
// Relative path
@@ -265,41 +237,38 @@ public static function file ( $file, $options = null ) {
return '';
}
- // Load the data of previously cached files to self::$config
- self::loadCacheData();
+ // Load the cache data
+ $process->cacheData = csscrush::io_call( 'getCacheData' );
- // Get the merged options, stored to self::$options
- $options = self::getOptions( $options );
-
- // Get the hostfile object
- $hostfile = self::getHostfile( $file );
-
- // Compiled filename we're searching for
- // This can be given as an option, uses the host-filename by default
- $baseCompileName = basename( $hostfile->name, '.css' );
- if ( !empty( $options[ 'output_file' ] ) ) {
- $baseCompileName = basename( $options[ 'output_file' ], '.css' );
+ // Get the input file object
+ if ( ! ( $process->input = csscrush::io_call( 'getInput', $file ) ) ) {
+ return '';
}
- self::$compileName = $baseCompileName . self::$COMPILE_SUFFIX;
- // If cache is enabled check for a valid compiled file
+ // Create a filename that will be used later
+ // Used in validateCache, and writing to filesystem
+ $process->outputFileName = csscrush::io_call( 'getOutputFileName' );
+
if ( $options[ 'cache' ] === true ) {
- $validCompliledFile = self::validateCache( $hostfile );
- if ( is_string( $validCompliledFile ) ) {
- return $validCompliledFile;
+
+ // If cache is enabled check for a valid compiled file
+ $valid_compliled_file = csscrush::io_call( 'validateExistingOutput' );
+
+ if ( is_string( $valid_compliled_file ) ) {
+ return $valid_compliled_file;
}
}
// Collate hostfile and imports
- $stream = csscrush_importer::hostfile( $hostfile );
+ $stream = csscrush_importer::hostfile( $process->input );
// Compile
$stream = self::compile( $stream );
- // Create file and return path. Return empty string on failure
- if ( file_put_contents( "$config->baseDir/" . self::$compileName, $stream ) ) {
- return "$config->baseURL/" . self::$compileName .
- ( $options[ 'versioning' ] ? '?' . time() : '' );
+ // Create file and return url. Return empty string on failure
+ if ( file_put_contents( "$process->outputDir/$process->outputFileName", $stream ) ) {
+ $timestamp = $options[ 'versioning' ] ? '?' . time() : '';
+ return "$process->outputDirUrl/$process->outputFileName$timestamp";
}
else {
return '';
@@ -315,15 +284,19 @@ public static function file ( $file, $options = null ) {
* @return string HTML link tag or error message inside HTML comment
*/
public static function tag ( $file, $options = null, $attributes = array() ) {
+
$file = self::file( $file, $options );
- if ( !empty( $file ) ) {
+
+ if ( ! empty( $file ) ) {
+
// On success return the tag with any custom attributes
$attributes[ 'rel' ] = "stylesheet";
$attributes[ 'href' ] = $file;
$attr_string = csscrush_util::htmlAttributes( $attributes );
return "<link$attr_string />\n";
}
else {
+
// Return an HTML comment with message on failure
$class = __CLASS__;
return "<!-- $class: File $file not found -->\n";
@@ -341,11 +314,14 @@ public static function tag ( $file, $options = null, $attributes = array() ) {
public static function inline ( $file, $options = null, $attributes = array() ) {
$file = self::file( $file, $options );
- if ( !empty( $file ) ) {
+
+ if ( ! empty( $file ) ) {
+
// On success fetch the CSS text
- $content = file_get_contents( self::$config->baseDir . '/' . self::$compileName );
+ $content = file_get_contents( self::$process->outputDir . '/' . self::$process->outputFileName );
$tag_open = '';
$tag_close = '';
+
if ( is_array( $attributes ) ) {
$attr_string = csscrush_util::htmlAttributes( $attributes );
$tag_open = "<style$attr_string>";
@@ -354,6 +330,7 @@ public static function inline ( $file, $options = null, $attributes = array() )
return "$tag_open{$content}$tag_close\n";
}
else {
+
// Return an HTML comment with message on failure
$class = __CLASS__;
return "<!-- $class: File $file not found -->\n";
@@ -368,28 +345,32 @@ public static function inline ( $file, $options = null, $attributes = array() )
* @return string CSS text
*/
public static function string ( $string, $options = null ) {
+
// Reset for current process
- self::reset();
- self::getOptions( $options );
+ self::reset( $options );
+
+ $config = self::$config;
+ $process = self::$process;
+ $options = self::$options;
// Set the path context if one is given
if ( isset( $options[ 'import_context' ] ) && ! empty( $options[ 'import_context' ] ) ) {
self::setPath( $options[ 'import_context' ] );
}
- // It's not associated with a real file so we create an 'empty' hostfile object
- $hostfile = self::getHostfile();
+ // It's not associated with a real file so we create an 'empty' input object
+ $process->input = csscrush::io_call( 'getInput' );
// Set the string on the object
- $hostfile->string = $string;
+ $process->input->string = $string;
// Import files may be ignored
if ( isset( $options[ 'no_import' ] ) ) {
- $hostfile->importIgnore = true;
+ $process->input->importIgnore = true;
}
// Collate imports
- $stream = csscrush_importer::hostfile( $hostfile );
+ $stream = csscrush_importer::hostfile( $process->input );
// Return compiled string
return self::compile( $stream );
@@ -401,19 +382,22 @@ public static function string ( $string, $options = null ) {
* @param mixed $var Assoc array of variable names and values, a php ini filename or null
*/
public static function globalVars ( $vars ) {
+
+ $config = self::$config;
+
// Merge into the stack, overrides existing variables of the same name
if ( is_array( $vars ) ) {
- self::$globalVars = array_merge( self::$globalVars, $vars );
+ $config->vars = array_merge( $config->vars, $vars );
}
// Test for a file. If it is attempt to parse it
elseif ( is_string( $vars ) and file_exists( $vars ) ) {
if ( $result = parse_ini_file( $vars ) ) {
- self::$globalVars = array_merge( self::$globalVars, $result );
+ $config->vars = array_merge( $config->vars, $result );
}
}
// Clear the stack if the argument is explicitly null
elseif ( is_null( $vars ) ) {
- self::$globalVars = array();
+ $config->vars = array();
}
}
@@ -423,26 +407,7 @@ public static function globalVars ( $vars ) {
* @param string $dir System path to the directory
*/
public static function clearCache ( $dir = '' ) {
- if ( empty( $dir ) ) {
- $dir = dirname( __FILE__ );
- }
- else if ( !file_exists( $dir ) ) {
- return;
- }
- $configPath = $dir . '/' . self::$config->file;
- if ( file_exists( $configPath ) ) {
- unlink( $configPath );
- }
- // Remove any compiled files
- $suffix = self::$COMPILE_SUFFIX;
- $suffixLength = strlen( $suffix );
- foreach ( scandir( $dir ) as $file ) {
- if (
- strpos( $file, $suffix ) === strlen( $file ) - $suffixLength
- ) {
- unlink( $dir . "/{$file}" );
- }
- }
+ return csscrush::io_call( 'clearCache', $dir );
}
@@ -483,32 +448,6 @@ public static function log () {
#####################
# Internal functions
- protected static function getHostfile ( $file = false ) {
- // May return a hostfile object associated with a real file
- // Alternatively it may return a hostfile object with string input
-
- $config = self::$config;
-
- // Make basic information about the hostfile accessible
- $hostfile = new stdClass;
- $hostfile->name = $file ? basename( $file ) : null;
- $hostfile->dir = $config->baseDir;
- $hostfile->path = $file ? "$config->baseDir/$hostfile->name" : null;
-
- if ( $file ) {
- if ( !file_exists( $hostfile->path ) ) {
- // If host file is not found return an empty string
- trigger_error( __METHOD__ . ": File '$hostfile->name' not found.\n", E_USER_WARNING );
- return '';
- }
- else {
- // Capture the modified time
- $hostfile->mtime = filemtime( $hostfile->path );
- }
- }
- return $hostfile;
- }
-
protected static function getBoilerplate () {
$file = csscrush_util::find( 'CssCrush-local.boilerplate', 'CssCrush.boilerplate' );
@@ -549,6 +488,7 @@ protected static function getOptions ( $options ) {
// Create default options for those not set
$option_defaults = array(
+
// Minify. Set true for formatting and comments
'debug' => false,
@@ -575,13 +515,12 @@ protected static function getOptions ( $options ) {
'rewrite_import_urls' => false,
// Keeping track of global vars internally
- '_globalVars' => self::$globalVars,
+ '_globalVars' => self::$config->vars,
);
- self::$options = is_array( $options ) ?
+ return is_array( $options ) ?
array_merge( $option_defaults, $options ) : $option_defaults;
- return self::$options;
}
protected static function pruneAliases () {
@@ -591,7 +530,7 @@ protected static function pruneAliases () {
// For expicit 'none' argument turn off aliases
if ( 'none' === $vendor ) {
- self::$aliases = null;
+ self::$config->aliases = null;
return;
}
@@ -605,7 +544,7 @@ protected static function pruneAliases () {
$vendor = "-$vendor-";
// Loop the aliases array, filter down to the target vendor
- foreach ( self::$aliases as $group_name => $group_array ) {
+ foreach ( self::$config->aliases as $group_name => $group_array ) {
// Property/value aliases are a special case
if ( 'values' === $group_name ) {
foreach ( $group_array as $property => $values ) {
@@ -617,7 +556,7 @@ protected static function pruneAliases () {
}
}
}
- self::$aliases[ 'values' ][ $property ][ $value ] = $result;
+ self::$config->aliases[ 'values' ][ $property ][ $value ] = $result;
}
continue;
}
@@ -630,14 +569,13 @@ protected static function pruneAliases () {
}
// Prune the whole alias keyword if there is no result
if ( empty( $result ) ) {
- unset( self::$aliases[ $group_name ][ $alias_keyword ] );
+ unset( self::$config->aliases[ $group_name ][ $alias_keyword ] );
}
else {
- self::$aliases[ $group_name ][ $alias_keyword ] = $result;
+ self::$config->aliases[ $group_name ][ $alias_keyword ] = $result;
}
}
}
- // self::log( self::$aliases );
}
protected static function calculateVariables () {
@@ -646,8 +584,8 @@ protected static function calculateVariables () {
// In-file variables override global variables
// Runtime variables override in-file variables
- self::$storage->variables = array_merge(
- self::$globalVars, self::$storage->variables );
+ self::$storage->variables = array_merge( self::$config->vars, self::$storage->variables );
+
if ( !empty( self::$options[ 'vars' ] ) ) {
self::$storage->variables = array_merge(
self::$storage->variables, self::$options[ 'vars' ] );
@@ -661,8 +599,8 @@ protected static function calculateVariables () {
$regex->function->var, array( 'self', 'cb_placeVariables' ), $value );
// Custom functions:
- // Variable values can be escaped from function parsing with a double bang
- if ( strpos( $value, '!!' ) === 0 ) {
+ // Variable values can be escaped from function parsing with a tilde prefix
+ if ( strpos( $value, '~' ) === 0 ) {
$value = ltrim( $value, "!\t\r " );
}
else {
@@ -684,20 +622,26 @@ protected static function placeVariables ( $stream ) {
return $stream;
}
- protected static function reset () {
+ protected static function reset ( $options = null ) {
+
// Reset properties for current process
self::$tokenUID = 0;
- self::$storage = new stdclass;
-
+
+ self::$process = new stdclass();
+ self::$process->cacheData = array();
+
+ self::$storage = new stdclass();
self::$storage->tokens = (object) array(
'strings' => array(),
'comments' => array(),
'rules' => array(),
'parens' => array(),
);
self::$storage->variables = array();
- // Temporary storage
- self::$storage->tmp = new stdclass;
+ self::$storage->misc = new stdclass();
+
+ // Load the merged options
+ self::$options = self::getOptions( $options );
}
protected static function compile ( $stream ) {
@@ -712,15 +656,15 @@ protected static function compile ( $stream ) {
}
// Set aliases. May be pruned if a vendor target is set
- self::$aliases = self::$aliasesRaw;
+ self::$config->aliases = self::$config->aliasesRaw;
self::pruneAliases();
// Parse variables
$stream = self::extractVariables( $stream );
// Calculate the variable stack
self::calculateVariables();
- self::log( self::$storage->variables );
+ // self::log( self::$storage->variables );
// Place the variables
$stream = self::placeVariables( $stream );
@@ -743,6 +687,22 @@ protected static function compile ( $stream ) {
// Alias at-rules (if there are any)
$stream = self::aliasAtRules( $stream );
+
+
+ $iterator = new csscrush_atrule_iterator( array(
+ 'input' => $stream,
+ 'search' => '@prefix',
+ 'direction' => 'reverse',
+ ));
+
+ // csscrush::log( $iterator );
+ // csscrush::log( count( $iterator ) );
+
+ foreach ( $iterator as $atrule ) {
+ // self::log( $value );
+ }
+
+
// print it all back
$stream = self::display( $stream );
@@ -751,7 +711,7 @@ protected static function compile ( $stream ) {
$stream = self::getBoilerplate() . "\n$stream";
}
- self::log( self::$config->data );
+ // self::log( self::$config->cacheData );
// Release memory
self::$storage = null;
@@ -760,7 +720,8 @@ protected static function compile ( $stream ) {
}
protected static function display ( $stream ) {
- $minify = !self::$options[ 'debug' ];
+
+ $minify = ! self::$options[ 'debug' ];
$regex = self::$regex;
if ( $minify ) {
@@ -811,80 +772,15 @@ protected static function display ( $stream ) {
return $stream;
}
- protected static function validateCache ( $hostfile ) {
- $config = self::$config;
-
- // Search base directory for an existing compiled file
- foreach ( scandir( $config->baseDir ) as $filename ) {
-
- if ( self::$compileName != $filename ) {
- continue;
- }
- // Cached file exists
- self::log( 'Cached file exists' );
-
- $existingfile = new stdClass;
- $existingfile->name = $filename;
- $existingfile->path = "$config->baseDir/$existingfile->name";
- $existingfile->URL = "$config->baseURL/$existingfile->name";
-
- // Start off with the host file then add imported files
- $all_files = array( $hostfile->mtime );
-
- if ( file_exists( $existingfile->path ) and isset( $config->data[ self::$compileName ] ) ) {
- // File exists and has config
- self::log( 'has config' );
- foreach ( $config->data[ $existingfile->name ][ 'imports' ] as $import_file ) {
- // Check if this is docroot relative or hostfile relative
- $root = strpos( $import_file, '/' ) === 0 ? $config->docRoot : $config->baseDir;
- $import_filepath = realpath( $root ) . "/{$import_file}";
- if ( file_exists( $import_filepath ) ) {
- $all_files[] = filemtime( $import_filepath );
- }
- else {
- // File has been moved, remove old file and skip to compile
- self::log( 'Import file has been moved, removing existing file' );
- unlink( $existingfile->path );
- return false;
- }
- }
-
- $existing_options = $config->data[ $existingfile->name ][ 'options' ];
- $existing_datesum = $config->data[ $existingfile->name ][ 'datem_sum' ];
- if (
- $existing_options == self::$options and
- $existing_datesum == array_sum( $all_files )
- ) {
- // Files have not been modified and config is the same: return the old file
- self::log( "Files and options have not been modified, returning existing
- file '$existingfile->URL'" );
- return $existingfile->URL . ( self::$options[ 'versioning' ] !== false ? "?{$existing_datesum}" : '' );
- }
- else {
- // Remove old file and continue making a new one...
- self::log( 'Files or options have been modified, removing existing file' );
- unlink( $existingfile->path );
- }
- }
- else if ( file_exists( $existingfile->path ) ) {
- // File exists but has no config
- self::log( 'File exists but no config, removing existing file' );
- unlink( $existingfile->path );
- }
- return false;
-
- } // foreach
- return false;
- }
-
protected static function minify ( $str ) {
$replacements = array(
- '!\n+| (\{)!' => '$1', // Trim whitespace
- '!(^|[: \(,])0(\.\d+)!' => '$1$2', // Strip leading zeros on floats
- '!(^|[: \(,])\.?0[a-zA-Z]{1,5}!i' => '${1}0', // Strip unnecessary units on zero values
- '!(^|\:) *(0 0 0|0 0 0 0) *(;|\})!' => '${1}0${3}', // Collapse zero lists
- '!(padding|margin) ?\: *0 0 *(;|\})!' => '${1}:0${2}', // Collapse zero lists continued
- '!\s*([>~+=])\s*!' => '$1', // Clean-up around combinators
+ '!\n+| (\{)!' => '$1', // Trim whitespace
+ '!(^|[: \(,])0(\.\d+)!' => '$1$2', // Strip leading zeros on floats
+ // '!(^|[: \(,])\.?0[a-zA-Z]{1,5}!i' => '${1}0', // Strip unnecessary units on zero values
+ '!(^|[: \(,])\.?0(in|[cme]m|ex|p[tcx])!i' => '${1}0', // Strip unnecessary units on zero values
+ '!(^|\:) *(0 0 0|0 0 0 0) *(;|\})!' => '${1}0${3}', // Collapse zero lists
+ '!(padding|margin) ?\: *0 0 *(;|\})!' => '${1}:0${2}', // Collapse zero lists continued
+ '!\s*([>~+=])\s*!' => '$1', // Clean-up around combinators
'!\#([0-9a-f])\1([0-9a-f])\2([0-9a-f])\3!i'
=> '#$1$2$3', // Compress hex codes
);
@@ -894,11 +790,11 @@ protected static function minify ( $str ) {
protected static function aliasAtRules ( $stream ) {
- if ( empty( self::$aliases[ 'at-rules' ] ) ) {
+ if ( empty( self::$config->aliases[ 'at-rules' ] ) ) {
return $stream;
}
- $aliases = self::$aliases[ 'at-rules' ];
+ $aliases = self::$config->aliases[ 'at-rules' ];
foreach ( $aliases as $at_rule => $at_rule_aliases ) {
if (
@@ -915,10 +811,11 @@ protected static function aliasAtRules ( $stream ) {
// Store the match position
$block_start_pos = $match[0][1];
+
// Capture the curly bracketed block
$curly_match = csscrush_util::matchBrackets( $stream, $brackets = array( '{', '}' ), $block_start_pos );
- if ( !$curly_match ) {
+ if ( ! $curly_match ) {
// Couldn't match the block
break;
}
@@ -961,7 +858,7 @@ protected static function aliasAtRules ( $stream ) {
$cloneRule->declarations = $new_set;
// Store the clone
- $replacements[] = $clone_rule_label = self::createTokenLabel( 'r' );
+ $replacements[] = $clone_rule_label = self::tokenLabelCreate( 'r' );
self::$storage->tokens->rules[ $clone_rule_label ] = $cloneRule;
}
// Finally replace the original labels with the cloned rule labels
@@ -989,17 +886,35 @@ protected static function aliasAtRules ( $stream ) {
return $stream;
}
- public static function createTokenLabel ( $prefix, $counter = null ) {
- $counter = !is_null( $counter ) ? $counter : ++self::$tokenUID;
+ public static function tokenLabelCreate ( $prefix ) {
+ $counter = ++self::$tokenUID;
return "___$prefix{$counter}___";
}
+ public static function tokenReplace ( $string, $token_replace, $type = 'parens' ) {
+
+ // The tokens to replace
+ $token_replace = (array) $token_replace;
+
+ // Reference the token table
+ $token_table =& self::$storage->tokens->{ $type };
+
+ // Replace the tokens listed
+ foreach ( $token_replace as $token ) {
+ if ( isset( $token_table[ $token ] ) ) {
+ $string = str_replace( $token, $token_table[ $token ], $string );
+ }
+ }
+
+ return $string;
+ }
+
#############################
# preg_replace callbacks
protected static function cb_extractStrings ( $match ) {
- $label = csscrush::createTokenLabel( 's' );
+ $label = csscrush::tokenLabelCreate( 's' );
csscrush::$storage->tokens->strings[ $label ] = $match[0];
return $label;
}
@@ -1018,7 +933,7 @@ protected static function cb_extractComments ( $match ) {
return '';
}
- $label = self::createTokenLabel( 'c' );
+ $label = self::tokenLabelCreate( 'c' );
self::$storage->tokens->comments[ $label ] = $comment;
return $label;
@@ -1029,6 +944,7 @@ protected static function cb_restoreComments ( $match ) {
}
protected static function cb_extractVariables ( $match ) {
+
$regex = self::$regex;
$block = $match[2];
@@ -1073,7 +989,7 @@ protected static function cb_placeVariables ( $match ) {
protected static function cb_extractAndProcessRules ( $match ) {
- $rule = new stdClass;
+ $rule = new stdclass();
$rule->selector_raw = $match[1];
$rule->declaration_raw = $match[2];
@@ -1082,11 +998,11 @@ protected static function cb_extractAndProcessRules ( $match ) {
$rule = new csscrush_rule( $rule->selector_raw, $rule->declaration_raw );
// Only store rules with declarations
- if ( !empty( $rule->declarations ) ) {
+ if ( ! empty( $rule->declarations ) ) {
csscrush_hook::run( 'rule_prealias', $rule );
- if ( !empty( self::$aliases ) ) {
+ if ( ! empty( self::$config->aliases ) ) {
$rule->addPropertyAliases();
$rule->addFunctionAliases();
$rule->addValueAliases();
@@ -1098,7 +1014,7 @@ protected static function cb_extractAndProcessRules ( $match ) {
csscrush_hook::run( 'rule_postprocess', $rule );
- $label = self::createTokenLabel( 'r' );
+ $label = self::tokenLabelCreate( 'r' );
self::$storage->tokens->rules[ $label ] = $rule;
return $label . "\n";
}
@@ -1112,21 +1028,25 @@ protected static function cb_restoreLiteral ( $match ) {
}
protected static function cb_printRule ( $match ) {
- $minify = !self::$options[ 'debug' ];
+
+ $minify = ! self::$options[ 'debug' ];
+ $whitespace = $minify ? '' : ' ';
+
$ruleLabel = $match[0];
- if ( !isset( self::$storage->tokens->rules[ $ruleLabel ] ) ) {
+
+ if ( ! isset( self::$storage->tokens->rules[ $ruleLabel ] ) ) {
return '';
}
$rule = self::$storage->tokens->rules[ $ruleLabel ];
// Build the selector
- $selectors = implode( ',', $rule->selectors );
+ $selectors = implode( ",$whitespace", $rule->selectors );
// Build the block
$block = array();
- $colon = $minify ? ':' : ': ';
foreach ( $rule as $declaration ) {
- $block[] = "{$declaration->property}$colon{$declaration->value}";
+ $important = $declaration->important ? "$whitespace!important" : '';
+ $block[] = "$declaration->property:{$whitespace}$declaration->value{$important}";
}
// Return whole rule
View
2 lib/Function.php
@@ -44,7 +44,7 @@ public static function parseAndExecuteValue ( $str ) {
}
// No custom functions, early return
- if ( !preg_match( $patt, $str ) ) {
+ if ( ! preg_match( $patt, $str ) ) {
return $str;
}
View
255 lib/IO.php
@@ -0,0 +1,255 @@
+<?php
+/**
+ *
+ * Interface for writing files, retrieving files and checking caches
+ *
+ */
+
+class csscrush_io {
+
+
+ // Any setup that needs to be done
+ public static function init () {
+
+ $process = csscrush::$process;
+
+ $process->cacheFileName = '.csscrush';
+ $process->cacheFilePath = "$process->inputDir/$process->cacheFileName";
+ }
+
+
+ public static function getInput ( $file = false ) {
+
+ // May return a hostfile object associated with a real file
+ // Alternatively it may return a hostfile object with string input
+
+ $process = csscrush::$process;
+
+ // Make basic information about the input object accessible
+ $input = new stdclass();
+ $input->name = $file ? basename( $file ) : null;
+ $input->dir = $file ? $process->inputDir : null;
+ $input->path = $file ? "$process->inputDir/$input->name" : null;
+
+ if ( $file ) {
+
+ if ( ! file_exists( $input->path ) ) {
+ // On failure return false with a message
+ trigger_error( __METHOD__ . ": File '$input->name' not found.\n", E_USER_WARNING );
+ return false;
+ }
+ else {
+ // Capture the modified time
+ $input->mtime = filemtime( $input->path );
+ }
+ }
+ return $input;
+ }
+
+
+ public static function getOutputDir () {
+ return csscrush::$process->inputDir;
+ }
+
+
+ public static function testOutputDir ( $write_test = true ) {
+
+ $output_dir = csscrush::$process->outputDir;
+ $pathtest = true;
+
+ if ( ! file_exists( $output_dir ) ) {
+ trigger_error( __METHOD__ . ": directory '$output_dir' doesn't exist.\n", E_USER_WARNING );
+ $pathtest = false;
+ }
+ else if ( $write_test and ! is_writable( $output_dir ) ) {
+ csscrush::log( 'Attempting to change permissions' );
+ if ( ! @chmod( $output_dir, 0755 ) ) {
+ trigger_error( __METHOD__ . ": directory '$output_dir' is unwritable.\n", E_USER_WARNING );
+ csscrush::log( 'Unable to update permissions' );
+ $pathtest = false;
+ }
+ else {
+ csscrush::log( 'Permissions updated' );
+ }
+ }
+ return $pathtest;
+ }
+
+
+ public static function getOutputFileName () {
+
+ $process = csscrush::$process;
+ $options = csscrush::$options;
+ $input = $process->input;
+
+ $output_basename = basename( $input->name, '.css' );
+
+ if ( ! empty( $options[ 'output_file' ] ) ) {
+ $output_basename = basename( $options[ 'output_file' ], '.css' );
+ }
+
+ return "$output_basename.crush.css";
+ }
+
+
+ public static function validateExistingOutput () {
+
+ $process = csscrush::$process;
+ $config = csscrush::$config;
+ $input = $process->input;
+
+ // Search base directory for an existing compiled file
+ foreach ( scandir( $process->outputDir ) as $filename ) {
+
+ if ( $process->outputFileName != $filename ) {
+ continue;
+ }
+ // Cached file exists
+ csscrush::log( 'Cached file exists' );
+
+ $existingfile = new stdclass();
+ $existingfile->name = $filename;
+ $existingfile->path = "$process->outputDir/$existingfile->name";
+ $existingfile->URL = "$process->outputDirUrl/$existingfile->name";
+
+ // Start off with the input file then add imported files
+ $all_files = array( $input->mtime );
+
+ if ( file_exists( $existingfile->path ) and isset( $process->cacheData[ $process->outputFileName ] ) ) {
+
+ // File exists and has config
+ csscrush::log( 'has config' );
+
+ foreach ( $process->cacheData[ $existingfile->name ][ 'imports' ] as $import_file ) {
+
+ // Check if this is docroot relative or input dir relative
+ $root = strpos( $import_file, '/' ) === 0 ? $config->docRoot : $process->inputDir;
+ $import_filepath = realpath( $root ) . "/$import_file";
+
+ if ( file_exists( $import_filepath ) ) {
+ $all_files[] = filemtime( $import_filepath );
+ }
+ else {
+ // File has been moved, remove old file and skip to compile
+ csscrush::log( 'Import file has been moved, removing existing file' );
+ unlink( $existingfile->path );
+ return false;
+ }
+ }
+
+ $existing_options = $process->cacheData[ $existingfile->name ][ 'options' ];
+ $existing_datesum = $process->cacheData[ $existingfile->name ][ 'datem_sum' ];
+
+ $options_unchanged = $existing_options == csscrush::$options;
+ $files_unchanged = $existing_datesum == array_sum( $all_files );
+
+ if ( $options_unchanged and $files_unchanged ) {
+
+ // Files have not been modified and config is the same: return the old file
+ csscrush::log( "Files and options have not been modified, returning existing
+ file '$existingfile->URL'" );
+ return $existingfile->URL . ( csscrush::$options[ 'versioning' ] !== false ? "?$existing_datesum" : '' );
+ }
+ else {
+ // Remove old file and continue making a new one...
+ ! $options_unchanged && csscrush::log( 'Options have been modified' );
+ ! $files_unchanged && csscrush::log( 'Files have been modified' );
+ csscrush::log( 'Removing existing file' );
+
+ unlink( $existingfile->path );
+ }
+ }
+ else if ( file_exists( $existingfile->path ) ) {
+ // File exists but has no config
+ csscrush::log( 'File exists but no config, removing existing file' );
+ unlink( $existingfile->path );
+ }
+ return false;
+
+ } // foreach
+
+ return false;
+ }
+
+
+ public static function clearCache ( $dir ) {
+
+ if ( empty( $dir ) ) {
+ $dir = dirname( __FILE__ );
+ }
+ else if ( ! file_exists( $dir ) ) {
+ return;
+ }
+
+ $configPath = $dir . '/' . csscrush::$process->cacheFilePath;
+ if ( file_exists( $configPath ) ) {
+ unlink( $configPath );
+ }
+
+ // Remove any compiled files
+ $suffix = '.crush.css';
+ $suffixLength = strlen( $suffix );
+
+ foreach ( scandir( $dir ) as $file ) {
+ if (
+ strpos( $file, $suffix ) === strlen( $file ) - $suffixLength
+ ) {
+ unlink( $dir . "/{$file}" );
+ }
+ }
+ }
+
+
+ public static function getCacheData () {
+
+ $config = csscrush::$config;
+ $process = csscrush::$process;
+
+ if (
+ file_exists( $process->cacheFilePath ) and
+ $process->cacheData and
+ $process->cacheData[ 'originPath' ] == $process->cacheFilePath
+ ) {
+ // Already loaded and config file exists in the current directory
+ return;
+ }
+
+ $cache_data_exists = file_exists( $process->cacheFilePath );
+ $cache_data_file_is_writable = $cache_data_exists ? is_writable( $process->cacheFilePath ) : false;
+
+ $cache_data = array();
+
+ if ( $cache_data_exists and $cache_data_file_is_writable ) {
+ // Load from file
+ $cache_data = unserialize( file_get_contents( $process->cacheFilePath ) );
+ }
+ else {
+ // Config file may exist but not be writable (may not be visible in some ftp situations?)
+ if ( $cache_data_exists ) {
+ if ( ! @unlink( $process->cacheFilePath ) ) {
+ trigger_error( __METHOD__ . ": Could not delete config data file.\n", E_USER_NOTICE );
+ }
+ }
+ // Create
+ csscrush::log( 'Creating cache data file' );
+ file_put_contents( $process->cacheFilePath, serialize( array() ) );
+ }
+
+ return $cache_data;
+ }
+
+
+ public static function saveCacheData () {
+
+ $process = csscrush::$process;
+
+ // Need to store the current path so we can check we're using the right config path later
+ $process->cacheData[ 'originPath' ] = $process->cacheFilePath;
+
+ csscrush::log( 'Saving config' );
+ file_put_contents( $process->cacheFilePath, serialize( $process->cacheData ) );
+ }
+
+}
+
+
View
88 lib/Importer.php
@@ -10,7 +10,7 @@ class csscrush_importer {
public static function save ( $data ) {
- $config = csscrush::$config;
+ $process = csscrush::$process;
$options = csscrush::$options;
// No saving if caching is disabled, return early
@@ -19,29 +19,33 @@ public static function save ( $data ) {
}
// Write to config
- $config->data[ csscrush::$compileName ] = $data;
-
- // Need to store the current path so we can check we're using the right config path later
- $config->data[ 'originPath' ] = $config->path;
+ $process->cacheData[ $process->outputFileName ] = $data;
// Save config changes
- file_put_contents( $config->path, serialize( $config->data ) );
+ csscrush::io_call( 'saveCacheData' );
}
- public static function hostfile ( $hostfile ) {
+ public static function hostfile () {
$config = csscrush::$config;
+ $process = csscrush::$process;
$options = csscrush::$options;
$regex = csscrush::$regex;
+ $hostfile = $process->input;
// Keep track of all import file info for later logging
$mtimes = array();
$filenames = array();
// Determine input; string or file
// Extract the comments then strings
- $stream = isset( $hostfile->string ) ? $hostfile->string : file_get_contents( $hostfile->path );
+ if ( isset( $hostfile->string ) ) {
+ $stream = $hostfile->string;
+ }
+ else {
+ $stream = file_get_contents( $hostfile->path );
+ }
// If there's a prepend file, prepend it
if ( $prependFile = csscrush_util::find( 'Prepend-local.css', 'Prepend.css' ) ) {
@@ -84,8 +88,6 @@ public static function hostfile ( $hostfile ) {
$url = $import_url_token->value;
}
- // csscrush::log( $match );
-
// Pass over absolute urls
// Move the search pointer forward
if ( preg_match( $regex->absoluteUrl, $url ) ) {
@@ -94,7 +96,7 @@ public static function hostfile ( $hostfile ) {
}
// Create import object
- $import = new stdClass;
+ $import = new stdclass();
$import->url = $url;
$import->mediaContext = $mediaContext;
$import->hostDir = $hostfile->dir;
@@ -112,18 +114,19 @@ public static function hostfile ( $hostfile ) {
// Failed to open import, just continue with the import line removed
if ( ! $import->content ) {
- csscrush::log( "Import file '$import->url' not found" );
+ csscrush::log( "Import file '$import->url' not found at '$import->path'" );
$stream = $preStatement . $postStatement;
continue;
}
- // Import file opened successfully so we process it:
- // We need to resolve import statement urls in all imported files since
- // they will be brought inline with the hostfile
else {
+ // Import file opened successfully so we process it:
+ // We need to resolve import statement urls in all imported files since
+ // they will be brought inline with the hostfile
- // Start with extracting comments in the import
+ // Start with extracting strings and comments in the import
$import->content = csscrush::extractComments( $import->content );
+ $import->content = csscrush::extractStrings( $import->content );
$import->dir = dirname( $import->url );
@@ -135,13 +138,30 @@ public static function hostfile ( $hostfile ) {
// Match all @import statements in the import content
// Store the replacements we might find
$matchCount = preg_match_all( $regex->import, $import->content, $matchAll,
- PREG_OFFSET_CAPTURE );
+ PREG_OFFSET_CAPTURE | PREG_SET_ORDER );
$replacements = array();
+
for ( $index = 0; $index < $matchCount; $index++ ) {
- $fullMatch = $matchAll[0][ $index ][0];
- $urlMatch = $matchAll[1][ $index ][0];
+ $fullMatch = $matchAll[ $index ][0][0];
+
+ // Url match may be at one of 2 positions
+ if ( $matchAll[ $index ][1][1] == -1 ) {
+ $urlMatch = $matchAll[ $index ][2][0];
+ }
+ else {
+ $urlMatch = $matchAll[ $index ][1][0];
+ }
+ // Url may be a string token
+ if ( $urlMatchToken = preg_match( $regex->token->string, $urlMatch ) ) {
+ // Store the token
+ $urlMatchToken = new csscrush_string( $urlMatch );
+ // Set $urlMatch to the actual value
+ $urlMatch = $urlMatchToken->value;
+ }
+
+ // Search and replace on the statement url
$search = $urlMatch;
$replace = "$import->dir/$urlMatch";
@@ -155,27 +175,35 @@ public static function hostfile ( $hostfile ) {
}
}
- // Trim the statement and set the resolved path
- $statement = trim( str_replace( $search, $replace, $fullMatch ) );
+ // The full revised statement for replacement
+ $statement = $fullMatch;
+
+ if ( $urlMatchToken and ! empty( $replace ) ) {
+ // Alter the stored token on internal hash table
+ $urlMatchToken->update( $replace );
+ }
+ else {
+ // Trim the statement and set the resolved path
+ $statement = trim( str_replace( $search, $replace, $fullMatch ) );
+ }
// Normalise import statement to be without url() syntax:
// This is so relative urls can easily be targeted later
$statement = self::normalizeImportStatement( $statement );
- $replacements[ $fullMatch ] = $statement;
+ if ( $fullMatch !== $statement ) {
+ $replacements[ $fullMatch ] = $statement;
+ }
}
// If we've stored any altered @import strings then we need to apply them
- if ( count( $replacements ) ) {
+ if ( $replacements ) {
$import->content = str_replace(
array_keys( $replacements ),
array_values( $replacements ),
$import->content );
}
- // Now @import urls have been adjusted extract strings
- $import->content = csscrush::extractStrings( $import->content );
-
// Optionally rewrite relative url and custom function data-uri references
if ( $options[ 'rewrite_import_urls' ] ) {
$import->content = self::rewriteImportRelativeUrls( $import );
@@ -233,12 +261,13 @@ protected static function normalizeImportStatement ( $statement ) {
protected static function resolveAbsolutePath ( $url ) {
$config = csscrush::$config;
+ $process = csscrush::$process;
if ( ! file_exists ( $config->docRoot . $url ) ) {
return false;
}
// Move upwards '..' by the number of slashes in baseURL to get a relative path
- $url = str_repeat( '../', substr_count( $config->baseURL, '/' ) ) . substr( $url, 1 );
+ $url = str_repeat( '../', substr_count( $process->inputDirUrl, '/' ) ) . substr( $url, 1 );
return $url;
}
@@ -252,7 +281,7 @@ protected static function rewriteImportRelativeUrls ( $import ) {
$hostDir = csscrush_util::normalizeSystemPath( $import->hostDir, true );
$importDir = csscrush_util::normalizeSystemPath( dirname( $import->path ), true );
- csscrush::$storage->tmp->relativeUrlPrefix = '';
+ csscrush::$storage->misc->relativeUrlPrefix = '';
$url_prefix = '';
if ( $importDir === $hostDir ) {
@@ -302,7 +331,7 @@ protected static function rewriteImportRelativeUrls ( $import ) {
// and prepend $relative_url_prefix
// Make $url_prefix accessible in callback scope
- csscrush::$storage->tmp->relativeUrlPrefix = $url_prefix;
+ csscrush::$storage->misc->relativeUrlPrefix = $url_prefix;
$url_function_patt = '!
([^a-z-]) # the preceeding character
@@ -361,3 +390,4 @@ protected static function cb_rewriteImportRelativeUrl ( $match ) {
}
}
+
View
63 lib/Iterator.php
@@ -0,0 +1,63 @@
+<?php
+/**
+ *
+ * Iterators used during parsing
+ *
+ */
+
+class csscrush_block_iterator implements Iterator, Countable {
+
+ protected $matches;
+ protected $options;
+ protected $index = 0;
+
+ function count () { return count( $this->matches ); }
+ function next () { $this->index++; }
+ function rewind () { $this->index = 0; }
+ function valid () { return isset( $this->matches[ $this->index ] ); }
+ function key () { return $this->index; }
+ function current () { return $this->matches[ $this->index ]; }
+}
+
+
+class csscrush_atrule_iterator extends csscrush_block_iterator {
+
+
+ public function __construct ( $options ) {
+
+ $this->options = (object) $options;
+
+ $stream = $this->options->input;
+ $search = $this->options->search;
+
+ $patt = '!' . $search . '\s?([^\{]*)\{!';
+ preg_match_all( $patt, $stream, $this->matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER );
+
+ if ( $this->options->direction === 'reverse' ) {
+ $this->matches = array_reverse( $this->matches );
+ }
+ }
+
+
+ function current () {
+ csscrush::log( $this->matches[ $this->index ] );
+
+ $match = $this->matches[ $this->index ];
+
+ $out = new stdclass();
+
+ $out->before = '';
+ $out->after = '';
+ $out->content = '';
+ $out->arguments = '';
+
+ return $out;
+ }
+
+}
+
+
+class csscrush_ruletoken_iterator extends csscrush_block_iterator {
+}
+
+
View
282 lib/Rule.php
@@ -19,10 +19,10 @@ public function __construct ( $selector_string = null, $declarations_string ) {
$regex = csscrush::$regex;
// Parse the selectors chunk
- if ( !empty( $selector_string ) ) {
+ if ( ! empty( $selector_string ) ) {
$selectors_match = csscrush_util::splitDelimList( $selector_string, ',' );
- $this->parens += $selectors_match->matches;
+ // $this->parens += $selectors_match->matches;
// Remove and store comments that sit above the first selector
// remove all comments between the other selectors
@@ -35,85 +35,37 @@ public function __construct ( $selector_string = null, $declarations_string ) {
$this->selectors = $selectors_match->list;
}
- // Apply any custom functions
- $declarations_string = csscrush_function::parseAndExecuteValue( $declarations_string );
-
// Parse the declarations chunk
// Need to split safely as there are semi-colons in data-uris
- $declarations_match = csscrush_util::splitDelimList( $declarations_string, ';' );
- $this->parens += $declarations_match->matches;
+ $declarations_match = csscrush_util::splitDelimList( $declarations_string, ';', true );
- // Parse declarations in to property/value pairs
+ // Split declarations in to property/value pairs
foreach ( $declarations_match->list as $declaration ) {
+
// Strip comments around the property
$declaration = preg_replace( $regex->token->comment, '', $declaration );
- // Store the property
+ // Extract the property part of the declaration
$colonPos = strpos( $declaration, ':' );
if ( $colonPos === false ) {
// If there is no colon it's malformed
continue;
}
-
- // The property name
$prop = trim( substr( $declaration, 0, $colonPos ) );
- // Test for escape tilde
- if ( $skip = strpos( $prop, '~' ) === 0 ) {
- $prop = substr( $prop, 1 );
- }
- // Store the property name
- $this->addProperty( $prop );
-
- // Store the property family
- // Store the vendor id, if one is present
- if ( preg_match( $regex->vendorPrefix, $prop, $vendor ) ) {
- $family = $vendor[2];
- $vendor = $vendor[1];
- }
- else {
- $vendor = null;
- $family = $prop;
- }
-
// Extract the value part of the declaration
$value = substr( $declaration, $colonPos + 1 );
$value = $value !== false ? trim( $value ) : $value;
- if ( $value === false or $value === '' ) {
- // We'll ignore declarations with empty values
- continue;
- }
-
- // Create an index of all functions in the current declaration
- if ( preg_match_all( $regex->function->match, $value, $functions ) > 0 ) {
- // csscrush::log( $functions );
- $out = array();
- foreach ( $functions[2] as $index => $fn_name ) {
- $out[] = $fn_name;
- }
- $functions = array_unique( $out );
- }
- else {
- $functions = array();
- }
- // Store the declaration
- $_declaration = (object) array(
- 'property' => $prop,
- 'family' => $family,
- 'vendor' => $vendor,
- 'functions' => $functions,
- 'value' => $value,
- 'skip' => $skip,
- );
- $this->declarations[] = $_declaration;
+ // Add declaration to the stack
+ $this->addDeclaration( $prop, $value );
}
}
public function addPropertyAliases () {
$regex = csscrush::$regex;
- $aliasedProperties =& csscrush::$aliases[ 'properties' ];
+ $aliasedProperties =& csscrush::$config->aliases[ 'properties' ];
// First test for the existence of any aliased properties
$intersect = array_intersect( array_keys( $aliasedProperties ), array_keys( $this->properties ) );
@@ -157,7 +109,7 @@ public function addPropertyAliases () {
public function addFunctionAliases () {
- $function_aliases =& csscrush::$aliases[ 'functions' ];
+ $function_aliases =& csscrush::$config->aliases[ 'functions' ];
$aliased_functions = array_keys( $function_aliases );
if ( empty( $aliased_functions ) ) {
@@ -239,7 +191,7 @@ public function addFunctionAliases () {
public function addValueAliases () {
- $aliasedValues =& csscrush::$aliases[ 'values' ];
+ $aliasedValues =& csscrush::$config->aliases[ 'values' ];
// First test for the existence of any aliased properties
$intersect = array_intersect( array_keys( $aliasedValues ), array_keys( $this->properties ) );
@@ -287,7 +239,7 @@ public function expandSelectors () {
preg_match( '!:any(___p\d+___)!', $selector, $m );
// Parse the arguments
- $expression = trim( $this->parens[ $m[1] ], '()' );
+ $expression = trim( csscrush::$storage->tokens->parens[ $m[1] ], '()' );
$parts = preg_split( $reg_comma, $expression, null, PREG_SPLIT_NO_EMPTY );
$tmp = array();
@@ -339,7 +291,7 @@ public function propertyCount ( $prop ) {
return 0;
}
- // Add property to the rule index keeping track of the count
+
public function addProperty ( $prop ) {
if ( isset( $this->properties[ $prop ] ) ) {
$this->properties[ $prop ]++;
@@ -349,27 +301,199 @@ public function addProperty ( $prop ) {
}
}
- public function createDeclaration ( $property, $value, $options = array() ) {
+
+ public function addDeclaration ( $prop, $value ) {
+
+ // $regex = csscrush::$regex;
+ //
+ // // Check the input
+ // if ( empty( $prop ) or $value === '' or $value === null ) {
+ // return false;
+ // }
+ //
+ // // Test for escape tilde
+ // if ( $skip = strpos( $prop, '~' ) === 0 ) {
+ // $prop = substr( $prop, 1 );
+ // }
+ //
+ // // Store the property family
+ // // Store the vendor id, if one is present
+ // if ( preg_match( $regex->vendorPrefix, $prop, $vendor ) ) {
+ // $family = $vendor[2];
+ // $vendor = $vendor[1];
+ // }
+ // else {
+ // $vendor = null;
+ // $family = $prop;
+ // }
+ //
+ // // Check for !important keywords
+ // if ( ( $important = strpos( $value, '!important' ) ) !== false ) {
+ // $value = substr( $value, 0, $important );
+ // $important = true;
+ // }
+ //
+ // // Ignore declarations with null css values
+ // if ( $value === false or $value === '' ) {
+ // return false;
+ // }
+ //
+ // // Apply custom functions
+ // if ( ! $skip ) {
+ // $value = csscrush_function::parseAndExecuteValue( $value );
+ // }
+ //
+ // // Tokenize all remaining paren pairs
+ // $match_obj = csscrush_util::matchAllBrackets( $value );
+ // $this->parens += $match_obj->matches;
+ // $value = $match_obj->string;
+ //
+ //
+ // // Create an index of all regular functions in the value
+ // if ( preg_match_all( $regex->function->match, $value, $functions ) > 0 ) {
+ // $out = array();
+ // foreach ( $functions[2] as $index => $fn_name ) {
+ // $out[] = $fn_name;
+ // }
+ // $functions = array_unique( $out );
+ // }
+ // else {
+ /