Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added IO interface for alternative methods of cache management

Updated Prepend.css and initial-values.ini
Internal refactoring
  • Loading branch information...
commit 3841bba6045dee7f96cc2990f4842bc0a8521c54 1 parent 0084fed
@peteboere authored
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,27 +61,32 @@ 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;
@@ -103,6 +94,21 @@ public static function init ( $current_dir ) {
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,8 +284,11 @@ 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;
@@ -324,6 +296,7 @@ public static function tag ( $file, $options = null, $attributes = array() ) {
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,11 +622,15 @@ 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(),
@@ -696,8 +638,10 @@ protected static function reset () {
'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,7 +656,7 @@ 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
@@ -720,7 +664,7 @@ protected static function compile ( $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,21 +19,20 @@ 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();
@@ -41,7 +40,12 @@ public static function hostfile ( $hostfile ) {
// 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 {
+ // $functions = array();
+ // }
+ //
+ // // Store the declaration
+ // $_declaration = (object) array(
+ // 'property' => $prop,
+ // 'family' => $family,
+ // 'vendor' => $vendor,
+ // 'functions' => $functions,
+ // 'value' => $value,
+ // 'skip' => $skip,
+ // 'important' => $important,
+ // );
+
+
+
+ // Store the property name
+ $this->addProperty( $prop );
+
+ // Add declaration to the stack
+ $_declaration = new csscrush_declaration( $prop, $value );
+ $this->declarations[] = $_declaration;
+
+ return $_declaration;
+ }
+
+ //
+ // // Get a declaration value without paren tokens
+ // public function getDeclarationValue ( $declaration ) {
+ // // $paren_keys = array_keys( $this->parens );
+ // // $paren_values = array_values( $this->parens );
+ //
+ // return csscrush::tokenReplace( $declaration->value, $token_replace );
+ //
+ // return str_replace( $paren_keys, $paren_values, $declaration->value );
+ // }
+
+}
+
+
+class csscrush_declaration {
+
+ public $property;
+ public $family;
+ public $vendor;
+ public $functions;
+ public $value;
+ public $skip;
+ public $important;
+ public $parenTokens;
+
+ public function __construct ( $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( $property, '~' ) === 0 ) {
- $property = substr( $property, 1 );
+ 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 );
}
- $_declaration = array(
- 'property' => $property,
- 'family' => null,
- 'vendor' => null,
- 'value' => $value,
- 'skip' => $skip,
- );
- $this->addProperty( $property );
- return (object) array_merge( $_declaration, $options );
+
+ // Tokenize all remaining paren pairs
+ $match_obj = csscrush_util::matchAllBrackets( $value );
+ $this->parenTokens = $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 {
+ $functions = array();
+ }
+
+ // // Store the declaration
+ // $_declaration = (object) array(
+ // 'property' => $prop,
+ // 'family' => $family,
+ // 'vendor' => $vendor,
+ // 'functions' => $functions,
+ // 'value' => $value,
+ // 'skip' => $skip,
+ // 'important' => $important,
+ // );
+
+ $this->property = $prop;
+ $this->family = $family;
+ $this->vendor = $vendor;
+ $this->functions = $functions;
+ $this->value = $value;
+ $this->skip = $skip;
+ $this->important = $important;
}
+
+ public function getFullValue () {
- // Get a declaration value without paren tokens
- public function getDeclarationValue ( $declaration ) {
- $paren_keys = array_keys( $this->parens );
- $paren_values = array_values( $this->parens );
- return str_replace( $paren_keys, $paren_values, $declaration->value );
+ return csscrush::tokenReplace( $this->value, $this->parenTokens );
}
-}
+}
+
+
+
View
30 lib/Util.php
@@ -31,7 +31,7 @@ public static function normalizeSystemPath ( $path, $stripMsDos = false ) {
public static function find () {
foreach ( func_get_args() as $file ) {
- $file_path = csscrush::$location . '/' . $file;
+ $file_path = csscrush::$config->location . '/' . $file;
if ( file_exists( $file_path ) ) {
return $file_path;
}
@@ -54,7 +54,7 @@ public static function normalizeWhiteSpace ( $str ) {
public static function splitDelimList ( $str, $delim, $fold_in = false, $allow_empty = false ) {
$match_obj = self::matchAllBrackets( $str );
-
+
// If the delimiter is one character do a simple split
// Otherwise do a regex split
if ( 1 === strlen( $delim ) ) {
@@ -63,16 +63,21 @@ public static function splitDelimList ( $str, $delim, $fold_in = false, $allow_e
else {
$match_obj->list = preg_split( '!' . $delim . '!', $match_obj->string );
}
-
+