Skip to content
Browse files

Added command line application

Reorganized aliases file with some additions
Initial-values updated
Added more internal utility methods
Slight internal changes for the command line application
Some internal method renaming for readability
Updated csscrush::string to handle import statements
feature: Added command line application
feature: 'rewrite_import_urls' option - Ability to rewrite relative url references inside imported css files
feature: Variable values can be excluded from function parsing by preceeding with '!!'
feature: CSS comments can be made private by adding a marker '$!' at the first character
Math function now surpresses parse errors, returns 0 on error
issue #21: workaround for php installations that do not support bcmath extension
feature: Prepend.css - A css file in csscrush root folder prepended to every input, by default populated only with variables
  • Loading branch information...
1 parent ed61165 commit 0c51ab0cfb977c3ffd95206911207443abaf37b7 @peteboere committed Feb 2, 2012
Showing with 1,015 additions and 472 deletions.
  1. +97 −81 Aliases.ini
  2. +12 −1 CHANGELOG
  3. +3 −3 CssCrush.php
  4. +3 −3 Plugins.ini
  5. +39 −0 Prepend.css
  6. +182 −0 cli.php
  7. +2 −2 lib/Color.php
  8. +162 −268 lib/Core.php
  9. +43 −29 lib/Function.php
  10. +2 −1 lib/Hook.php
  11. +236 −67 lib/Importer.php
  12. +11 −11 lib/Rule.php
  13. +218 −5 lib/Util.php
  14. +5 −1 misc/initial-values.ini
View
178 Aliases.ini
@@ -7,50 +7,6 @@
[properties]
- ; Border radius
- border-radius[] = -webkit-border-radius
- border-radius[] = -moz-border-radius
- border-top-left-radius[] = -webkit-border-top-left-radius
- border-top-left-radius[] = -moz-border-radius-topleft
- border-top-right-radius[] = -webkit-border-top-right-radius
- border-top-right-radius[] = -moz-border-radius-topright
- border-bottom-left-radius[] = -webkit-border-bottom-left-radius
- border-bottom-left-radius[] = -moz-border-radius-bottomleft
- border-bottom-right-radius[] = -webkit-border-bottom-right-radius
- border-bottom-right-radius[] = -moz-border-radius-bottomright
-
- ; Box shadow
- box-shadow[] = -webkit-box-shadow
- box-shadow[] = -moz-box-shadow
-
- ; Transforms
- transform[] = -webkit-transform
- transform[] = -moz-transform
- transform[] = -ms-transform
- transform[] = -o-transform
-
- ; Transitions
- transition[] = -webkit-transition
- transition[] = -moz-transition
- transition[] = -ms-transition
- transition[] = -o-transition
- transition-delay[] = -webkit-transition-delay
- transition-delay[] = -moz-transition-delay
- transition-delay[] = -ms-transition-delay
- transition-delay[] = -o-transition-delay
- transition-duration[] = -webkit-transition-duration
- transition-duration[] = -moz-transition-duration
- transition-duration[] = -ms-transition-duration
- transition-duration[] = -o-transition-duration
- transition-property[] = -webkit-transition-property
- transition-property[] = -moz-transition-property
- transition-property[] = -ms-transition-property
- transition-property[] = -o-transition-property
- transition-timing-function[] = -webkit-transition-timing-function
- transition-timing-function[] = -moz-transition-timing-function
- transition-timing-function[] = -ms-transition-timing-function
- transition-timing-function[] = -o-transition-timing-function
-
; Animations
animation[] = -webkit-animation
animation[] = -moz-animation
@@ -80,10 +36,10 @@
animation-timing-function[] = -moz-animation-timing-function
animation-timing-function[] = -ms-animation-timing-function
-
- ; Background size
- background-size[] = -webkit-background-size
- background-size[] = -moz-background-size
+ ; Backface visibility
+ backface-visibility[] = -webkit-backface-visibility
+ backface-visibility[] = -moz-backface-visibility
+ backface-visibility[] = -ms-backface-visibility
; Background clip
background-clip[] = -webkit-background-clip
@@ -93,6 +49,48 @@
background-origin[] = -webkit-background-origin
background-origin[] = -moz-background-origin
+ ; Background size
+ background-size[] = -webkit-background-size
+ background-size[] = -moz-background-size
+
+ ; Border radius
+ border-radius[] = -webkit-border-radius
+ border-radius[] = -moz-border-radius
+ border-top-left-radius[] = -webkit-border-top-left-radius
+ border-top-left-radius[] = -moz-border-radius-topleft
+ border-top-right-radius[] = -webkit-border-top-right-radius
+ border-top-right-radius[] = -moz-border-radius-topright
+ border-bottom-left-radius[] = -webkit-border-bottom-left-radius
+ border-bottom-left-radius[] = -moz-border-radius-bottomleft
+ border-bottom-right-radius[] = -webkit-border-bottom-right-radius
+ border-bottom-right-radius[] = -moz-border-radius-bottomright
+
+ ; Border-image
+ border-image[] = -webkit-border-image
+ border-image[] = -moz-border-image
+ border-image[] = -o-border-image
+
+ ; Flexbox (old, but supported implementation)
+ box-align[] = -webkit-box-align
+ box-align[] = -moz-box-align
+ box-align[] = -ms-box-align
+ box-direction[] = -webkit-box-direction
+ box-direction[] = -moz-box-direction
+ box-direction[] = -ms-box-direction
+ box-flex[] = -webkit-box-flex
+ box-flex[] = -moz-box-flex
+ box-flex[] = -ms-box-flex
+ box-orient[] = -webkit-box-orient
+ box-orient[] = -moz-box-orient
+ box-orient[] = -ms-box-orient
+ box-pack[] = -webkit-box-pack
+ box-pack[] = -moz-box-pack
+ box-pack[] = -ms-box-pack
+
+ ; Box shadow
+ box-shadow[] = -webkit-box-shadow
+ box-shadow[] = -moz-box-shadow
+
; Box sizing
box-sizing[] = -webkit-box-sizing
box-sizing[] = -moz-box-sizing
@@ -120,57 +118,75 @@
column-span[] = -moz-column-span
column-width[] = -webkit-column-width
column-width[] = -moz-column-width
-
-
- ; Border-image
- border-image[] = -webkit-border-image
- border-image[] = -moz-border-image
- border-image[] = -o-border-image
-
- ; Flexbox (old, but supported implementation)
- box-align[] = -webkit-box-align
- box-align[] = -moz-box-align
- box-align[] = -ms-box-align
- box-direction[] = -webkit-box-direction
- box-direction[] = -moz-box-direction
- box-direction[] = -ms-box-direction
- box-flex[] = -webkit-box-flex
- box-flex[] = -moz-box-flex
- box-flex[] = -ms-box-flex
- box-orient[] = -webkit-box-orient
- box-orient[] = -moz-box-orient
- box-orient[] = -ms-box-orient
- box-pack[] = -webkit-box-pack
- box-pack[] = -moz-box-pack
- box-pack[] = -ms-box-pack
; Hyphens
hyphens[] = -webkit-hyphens
hyphens[] = -moz-hyphens
hyphens[] = -ms-hyphens
+ ; Perspective
+ perspective[] = -webkit-perspective
+ perspective[] = -moz-perspective
+ perspective[] = -ms-perspective
+ perspective-origin[] = -webkit-perspective-origin
+ perspective-origin[] = -moz-perspective-origin
+ perspective-origin[] = -ms-perspective-origin
+
+ ; Tab size
+ tab-size[] = -webkit-tab-size
+ tab-size[] = -moz-tab-size
+ tab-size[] = -o-tab-size
+
; Text decoration
text-decoration-color[] = -moz-text-decoration-color
text-decoration-line[] = -moz-text-decoration-line
text-decoration-style[] = -moz-text-decoration-style
+ ; Transforms
+ transform[] = -webkit-transform
+ transform[] = -moz-transform
+ transform[] = -ms-transform
+ transform[] = -o-transform
+ transform-style[] = -webkit-transform-style
+ transform-style[] = -moz-transform-style
+ transform-style[] = -ms-transform-style
+
+ ; Transitions
+ transition[] = -webkit-transition
+ transition[] = -moz-transition
+ transition[] = -ms-transition
+ transition[] = -o-transition
+ transition-delay[] = -webkit-transition-delay
+ transition-delay[] = -moz-transition-delay
+ transition-delay[] = -ms-transition-delay
+ transition-delay[] = -o-transition-delay
+ transition-duration[] = -webkit-transition-duration
+ transition-duration[] = -moz-transition-duration
+ transition-duration[] = -ms-transition-duration
+ transition-duration[] = -o-transition-duration
+ transition-property[] = -webkit-transition-property
+ transition-property[] = -moz-transition-property
+ transition-property[] = -ms-transition-property
+ transition-property[] = -o-transition-property
+ transition-timing-function[] = -webkit-transition-timing-function
+ transition-timing-function[] = -moz-transition-timing-function
+ transition-timing-function[] = -ms-transition-timing-function
+ transition-timing-function[] = -o-transition-timing-function
+
; User select (non standard)
user-select[] = -webkit-user-select
user-select[] = -moz-user-select
user-select[] = -ms-user-select
user-select[] = -o-user-select
user-select[] = user-select
- ; Tab size
- tab-size[] = -webkit-tab-size
- tab-size[] = -moz-tab-size
- tab-size[] = -o-tab-size
-
;----------------------------------------------------------------------------
-;-- Value aliases
+;-- Property:value aliases
[values]
+
+ ; Flexbox
display:box[] = -webkit-box
display:box[] = -moz-box
display:box[] = -ms-box
@@ -181,6 +197,10 @@
[functions]
+ ; Calc
+ calc[] = -webkit-calc
+ calc[] = -moz-calc
+
; Gradients
linear-gradient[] = -webkit-linear-gradient
linear-gradient[] = -moz-linear-gradient
@@ -201,10 +221,6 @@
repeating-radial-gradient[] = -ms-repeating-radial-gradient
repeating-radial-gradient[] = -o-repeating-radial-gradient
- ; Calc
- calc[] = -webkit-calc
- calc[] = -moz-calc
-
;----------------------------------------------------------------------------
;-- @rule aliases
View
13 CHANGELOG
@@ -1,5 +1,16 @@
-1.4
+1.4.1
-----
+Added command line application
+Added 'rewrite_import_urls' option - Ability to rewrite relative url references inside imported css files
+Added Prepend.css - Optionally prepend css to every input
+Fix for issue #21
+Reorganized aliases file with some additions
+Initial-values updated
+Updated csscrush::string method to correctly handle import statements
+
+
+1.4
+---
Added initial-keyword plugin (shim for the CSS3 keyword)
Added inline method (Issue #18)
Added ability to escape declarations from aliasing or plugins by prefixing with tilde
View
6 CssCrush.php
@@ -4,11 +4,11 @@
* CSS Crush
* Extensible CSS preprocessor
*
- * @version 1.4
+ * @version 1.4.1
* @license http://www.opensource.org/licenses/mit-license.php (MIT)
- * @copyright Copyright 2010-2011 Pete Boere
+ * @copyright Copyright 2010-2012 Pete Boere
+ *
*
- * @example
* <?php
*
* // Basic usage
View
6 Plugins.ini
@@ -9,7 +9,7 @@
plugins[] = ie-inline-block.php
; clip property shim for IE < 8
-plugins[] = ie-clip.php
+; plugins[] = ie-clip.php
; IE filter shorthand
; plugins[] = ie-filter.php
@@ -18,10 +18,10 @@ plugins[] = ie-clip.php
; plugins[] = ie-opacity.php
; Opaque fallback colors for clients that don't support RGBA
-plugins[] = rgba-fallback.php
+; plugins[] = rgba-fallback.php
; HSL shim - converts HSL values to hex codes
-plugins[] = hsl-to-hex.php
+; plugins[] = hsl-to-hex.php
; Compiles pseudo element double colon syntax to single colon for backwards compatibility
plugins[] = double-colon.php
View
39 Prepend.css
@@ -0,0 +1,39 @@
+/*$!
+
+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.
+
+*/
+
+@define {
+
+ /*------------------------
+ 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 );
+
+ /* 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;
+
+}
+
+
+
+
View
182 cli.php
@@ -0,0 +1,182 @@
+<?php
+/**
+ *
+ * Command line application
+ *
+ */
+
+require_once 'CssCrush.php';
+
+// error_reporting('-1');
+
+// stdin
+$stdin = fopen( 'php://stdin', 'r' );
+stream_set_blocking( $stdin, false ) or die ( 'Failed to disable stdin blocking' );
+$stdin_contents = stream_get_contents( $stdin );
+
+// stdout
+$stdout = fopen( 'php://stdout', 'w' );
+
+
+##################################################################
+## Version detection
+
+$version = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION;
+$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 );
+}
+
+
+##################################################################
+## Options
+
+$short_opts = array(
+ "f::", // Input file. Defaults to sdtin
+ "o::", // Output file. Defaults to stdout
+ "p", // Pretty formatting
+ 'b', // Output boilerplate
+ 'h', // Display help
+);
+
+$long_opts = array(
+ 'file::', // Input file. Defaults to sdtin
+ 'output::', // Output file. Defaults to stdout
+ 'pretty', // Pretty formatting
+ 'boilerplate', // Output boilerplate
+ 'help', // Display help
+ 'vendor-target:', // Vendor target
+ 'variables:', // Map of variable names in an http query string format
+);
+
+$opts = getopt( implode( $short_opts ), $long_opts );
+
+$input_file = @( $opts['f'] ?: $opts['file'] );
+$output_file = @( $opts['o'] ?: $opts['output'] );
+$variables = @$opts['variables'];
+$vendor_target = @$opts['vendor-target'];
+$boilerplate = @( isset( $opts['b'] ) ?: isset( $opts['boilerplate'] ) );
+$pretty = @( isset( $opts['p'] ) ?: isset( $opts['pretty'] ) );
+$help_flag = @( isset( $opts['h'] ) ?: isset( $opts['help'] ) );
+
+
+##################################################################
+## Help page
+
+// $command = $argv[0] == 'csscrush' ? 'csscrush' : 'php path/to/CssCrush/cli.php';
+$command = 'csscrush';
+
+$help = <<<TPL
+
+synopsis:
+ csscrush [-f|--file] [-o|--output-file] [-p|--pretty] [-b|--boilerplate]
+ [-h|--help] [--variables] [--vendor-target]
+
+options:
+ -f, --file:
+ The input file, if omitted takes input from stdin
+
+ -o, --output:
+ The output file, if omitted prints to stdout
+
+ -p, --pretty:
+ Formatted, unminified output
+
+ -b, --boilerplate:
+ Whether or not to output a boilerplate
+
+ -h, --help:
+ Display this help mesasge
+
+ --variables:
+ Map of variable names in an http query string format
+
+ --vendor-target:
+ Set to 'all' for all vendor prefixes (default)
+ Set to 'none' for no vendor prefixes
+ Set to a specific vendor prefix
+
+examples:
+ $command -f=styles.css --pretty --vendor-target=webkit
+
+ # Piping on unix based terminals
+ cat 'styles.css' | $command --boilerplate
+
+
+TPL;
+
+
+if ( $help_flag ) {
+ fwrite( $stdout, $help );
+ exit( 1 );
+}
+
+
+##################################################################
+## Input
+
+$input = null;
+
+if ( $input_file ) {
+ if ( ! file_exists( $input_file ) ) {
+ fwrite( $stdout, "can't find input file\n\n" );
+ exit( 0 );
+ }
+ $input = file_get_contents( $input_file );
+}
+elseif ( $stdin_contents ) {
+ $input = $stdin_contents;
+}
+else {
+ fwrite( $stdout, $help );
+ exit( 1 );
+}
+
+// Check if specified output file is valid before processing
+if ( $output_file ) {
+ if ( ! file_exists( $output_file ) ) {
+ fwrite( $stdout, "can't find output file\n\n" );
+ exit( 0 );
+ }
+}
+
+
+##################################################################
+## Processing
+
+$process_opts = array();
+if ( $vendor_target ) {
+ $process_opts[ 'vendor_target' ] = $vendor_target;
+}
+if ( $variables ) {
+ parse_str( $variables, $in_vars );
+ $process_opts[ 'vars' ] = $in_vars;
+}
+$process_opts[ 'boilerplate' ] = $boilerplate ? true : false;
+$process_opts[ 'debug' ] = $pretty ? true : false;
+$process_opts[ 'rewrite_import_urls' ] = true;
+
+$import_context = $input_file ? dirname( realpath( $input_file ) ) : null;
+
+$output = csscrush::string( $input, $process_opts, $import_context );
+
+
+##################################################################
+## Output
+
+if ( $output_file ) {
+ file_put_contents( $output_file, $output );
+}
+else {
+ fwrite( $stdout, $output . "\n" );
+}
+exit( 1 );
+
+
+
+
+
+
+
+
View
4 lib/Color.php
@@ -5,14 +5,14 @@
*
*/
-class CssCrush_Color {
+class csscrush_color {
protected static $keywords = array();
public static function getKeywords () {
// Load the keywords if necessary
if ( empty( self::$keywords ) ) {
- $path = CssCrush::$location . '/misc/color-keywords.ini';
+ $path = csscrush::$location . '/misc/color-keywords.ini';
if ( $keywords = parse_ini_file( $path ) ) {
foreach ( $keywords as $word => $rgb ) {
$rgb = array_map( 'intval', explode( ',', $rgb ) );
View
430 lib/Core.php
@@ -5,7 +5,7 @@
*
*/
-class CssCrush {
+class csscrush {
// Path information, global settings
public static $config;
@@ -52,7 +52,6 @@ class CssCrush {
'token' => array(
'comment' => '!___c\d+___!',
'string' => '!___s\d+___!',
- 'import' => '!___i\d+___!',
'rule' => '!___r\d+___!',
'paren' => '!___p\d+___!',
),
@@ -66,6 +65,7 @@ class CssCrush {
'match' => '!(^|[^a-z0-9_-])([a-z_-]+)(___p\d+___)!i',
),
'vendorPrefix' => '!^-([a-z]+)-([a-z-]+)!',
+ 'absoluteUrl' => '!^https?://!',
);
// Init called once manually post class definition
@@ -91,7 +91,7 @@ public static function init ( $current_dir ) {
$fullpath = realpath( basename( $scriptname ) );
$docRoot = substr( $fullpath, 0, stripos( $fullpath, $scriptname ) );
}
- $config->docRoot = rtrim( str_replace( '\\', '/', $docRoot ), '/' );
+ $config->docRoot = csscrush_util::normalizeSystemPath( $docRoot );
// Casting to objects for ease of use
self::$regex = (object) self::$regex;
@@ -104,13 +104,10 @@ protected static function loadAssets () {
// Find an aliases file in the root directory
// a local file will overrides the default
- $aliases_file = self::$location . '/Aliases.ini';
- if ( file_exists( self::$location . '/Aliases-local.ini' ) ) {
- $aliases_file = self::$location . '/Aliases-local.ini';
- }
+ $aliases_file = csscrush_util::find( 'Aliases-local.ini', 'Aliases.ini' );
// Load aliases file if it exists
- if ( file_exists( $aliases_file ) ) {
+ if ( $aliases_file ) {
if ( $result = @parse_ini_file( $aliases_file, true ) ) {
self::$aliasesRaw = $result;
@@ -134,13 +131,10 @@ protected static function loadAssets () {
// Find a plugins file in the root directory
// a local file will overrides the default
- $plugins_file = self::$location . '/Plugins.ini';
- if ( file_exists( self::$location . '/Plugins-local.ini' ) ) {
- $plugins_file = self::$location . '/Plugins-local.ini';
- }
+ $plugins_file = csscrush_util::find( 'Plugins-local.ini', 'Plugins.ini' );
// Load plugins
- if ( file_exists( $plugins_file ) ) {
+ if ( $plugins_file ) {
if ( $result = @parse_ini_file( $plugins_file ) ) {
foreach ( $result[ 'plugins' ] as $plugin_file ) {
$path = self::$location . "/plugins/$plugin_file";
@@ -158,8 +152,8 @@ protected static function loadAssets () {
}
}
- // Initialize config data, create config file if needed
- protected static function loadConfig () {
+ // Initialize config data, create config cache file if needed
+ protected static function loadCacheData () {
$config = self::$config;
if (
file_exists( $config->path ) and
@@ -181,31 +175,33 @@ protected static function loadConfig () {
// 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 file.\n", E_USER_NOTICE );
+ trigger_error( __METHOD__ . ": Could not delete config data file.\n", E_USER_NOTICE );
}
}
// Create
- self::log( 'Creating config file' );
+ self::log( 'Creating config data file' );
file_put_contents( $config->path, serialize( array() ) );
$config->data = array();
}
}
- // Establish the host file directory and ensure it's writable
- protected static function setPath ( $new_dir ) {
+ // Establish the hostfile directory and optionally test it's writable
+ protected static function setPath ( $new_dir, $write_test = true ) {
+
$config = self::$config;
$docRoot = $config->docRoot;
+
if ( strpos( $new_dir, $docRoot ) !== 0 ) {
// Not a system path
$new_dir = realpath( "$docRoot/$new_dir" );
}
$pathtest = true;
- if ( !file_exists( $new_dir ) ) {
+ if ( ! file_exists( $new_dir ) ) {
trigger_error( __METHOD__ . ": directory '$new_dir' doesn't exist.\n", E_USER_WARNING );
$pathtest = false;
}
- else if ( !is_writable( $new_dir ) ) {
+ 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 );
@@ -220,6 +216,7 @@ protected static function setPath ( $new_dir ) {
$config->path = "$new_dir/$config->file";
$config->baseDir = $new_dir;
$config->baseURL = substr( $new_dir, strlen( $docRoot ) );
+
return $pathtest;
}
@@ -259,31 +256,19 @@ public static function file ( $file, $options = null ) {
$pathtest = self::setPath( dirname( dirname( __FILE__ ) . '/' . $file ) );
}
- if ( !$pathtest ) {
- // Main directory not found or is not writable
- // Return an empty string
+ if ( ! $pathtest ) {
+ // Main directory not found or is not writable return an empty string
return '';
}
- self::loadConfig();
- self::parseOptions( $options );
- $options = self::$options;
+ // Load the data of previously cached files to self::$config
+ self::loadCacheData();
- // Make basic information about the hostfile accessible
- $hostfile = new stdClass;
- $hostfile->name = basename( $file );
- $hostfile->dir = $config->baseDir;
- $hostfile->path = "$config->baseDir/$hostfile->name";
+ // Get the merged options, stored to self::$options
+ $options = self::getOptions( $options );
- 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 );
- }
+ // 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
@@ -302,15 +287,10 @@ public static function file ( $file, $options = null ) {
}
// Collate hostfile and imports
- $stream = CssCrush_Importer::hostfile( $hostfile );
+ $stream = csscrush_importer::hostfile( $hostfile );
// Compile
- $stream = self::compile( $stream, true );
-
- // Add in boilerplate
- if ( $options[ 'boilerplate' ] ) {
- $stream = self::getBoilerplate() . "\n$stream";
- }
+ $stream = self::compile( $stream );
// Create file and return path. Return empty string on failure
if ( file_put_contents( "$config->baseDir/" . self::$compileName, $stream ) ) {
@@ -336,7 +316,7 @@ public static function tag ( $file, $options = null, $attributes = array() ) {
// On success return the tag with any custom attributes
$attributes[ 'rel' ] = "stylesheet";
$attributes[ 'href' ] = $file;
- $attr_string = CssCrush_Util::attributes( $attributes );
+ $attr_string = csscrush_util::htmlAttributes( $attributes );
return "<link $attr_string />\n";
}
else {
@@ -355,14 +335,15 @@ public static function tag ( $file, $options = null, $attributes = array() ) {
* @return string HTML link tag or error message inside HTML comment
*/
public static function inline ( $file, $options = null, $attributes = array() ) {
+
$file = self::file( $file, $options );
if ( !empty( $file ) ) {
// On success fetch the CSS text
$content = file_get_contents( self::$config->baseDir . '/' . self::$compileName );
$tag_open = '';
$tag_close = '';
if ( is_array( $attributes ) ) {
- $attr_string = CssCrush_Util::attributes( $attributes );
+ $attr_string = csscrush_util::htmlAttributes( $attributes );
$tag_open = "<style$attr_string>";
$tag_close = '</style>';
}
@@ -380,13 +361,30 @@ public static function inline ( $file, $options = null, $attributes = array() )
*
* @param string $string CSS text
* @param mixed $options An array of options or null
+ * @param string $import_context A context path for imports
* @return string CSS text
*/
- public static function string ( $string, $options = null ) {
+ public static function string ( $string, $options = null, $import_context = null ) {
// Reset for current process
self::reset();
- self::parseOptions( $options );
- return self::compile( $string );
+ self::getOptions( $options );
+
+ // Set the path context if one is given
+ if ( $import_context ) {
+ self::setPath( $import_context );
+ }
+
+ // It's not associated with a real file so we create an 'empty' hostfile object
+ $hostfile = self::getHostfile();
+
+ // Set the string on the object
+ $hostfile->string = $string;
+
+ // Collate imports
+ $stream = csscrush_importer::hostfile( $hostfile );
+
+ // Return compiled string
+ return self::compile( $stream );
}
/**
@@ -399,7 +397,7 @@ public static function globalVars ( $vars ) {
if ( is_array( $vars ) ) {
self::$globalVars = array_merge( self::$globalVars, $vars );
}
- // Is it a file? If yes attempt to parse it
+ // 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 );
@@ -441,17 +439,17 @@ public static function clearCache ( $dir = '' ) {
#####################
- # Developer
+ # Developer related
- // Enable logging
public static $logging = false;
- // Print the log
public static function log () {
- if ( !self::$logging ) {
+
+ if ( ! self::$logging ) {
return;
}
static $log = '';
+
$args = func_get_args();
if ( !count( $args ) ) {
// No arguments, return the log
@@ -477,19 +475,49 @@ 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 () {
- if (
- !( $boilerplate = file_get_contents( self::$location . "/CssCrush.boilerplate" ) ) or
- !self::$options[ 'boilerplate' ]
- ) {
+
+ $file = csscrush_util::find( 'CssCrush-local.boilerplate', 'CssCrush.boilerplate' );
+
+ if ( ! $file or ! self::$options[ 'boilerplate' ] ) {
return '';
}
+
+ // Load the file
+ $boilerplate = file_get_contents( $file );
+
// Process any tags, currently only '{{datetime}}' is supported
if ( preg_match_all( '!\{\{([^}]+)\}\}!', $boilerplate, $boilerplate_matches ) ) {
$replacements = array();
foreach ( $boilerplate_matches[0] as $index => $tag ) {
if ( $boilerplate_matches[1][$index] === 'datetime' ) {
- $replacements[] = date( 'Y-m-d H:i:s O' );
+ $replacements[] = @date( 'Y-m-d H:i:s O' );
}
else {
$replacements[] = '?';
@@ -509,30 +537,43 @@ protected static function getBoilerplate () {
TPL;
}
- protected static function parseOptions ( $options ) {
+ protected static function getOptions ( $options ) {
+
// Create default options for those not set
$option_defaults = array(
// Minify. Set true for formatting and comments
- 'debug' => false,
+ 'debug' => false,
+
// Append 'checksum' to output file name
- 'versioning' => true,
+ 'versioning' => true,
+
// Use the template boilerplate
'boilerplate' => true,
+
// Variables passed in at runtime
- 'vars' => array(),
+ 'vars' => array(),
+
// Enable/disable the cache
- 'cache' => true,
+ 'cache' => true,
+
// Output file. Defaults the host-filename
'output_file' => null,
+
// Vendor target. Only apply prefixes for a specific vendor, set to 'none' for no prefixes
'vendor_target' => 'all',
+
+ // Whether to rewrite the url references inside imported files
+ // This will be 'true' by default eventually
+ 'rewrite_import_urls' => false,
+
// Keeping track of global vars internally
'_globalVars' => self::$globalVars,
);
self::$options = is_array( $options ) ?
array_merge( $option_defaults, $options ) : $option_defaults;
+ return self::$options;
}
protected static function pruneAliases () {
@@ -607,13 +648,18 @@ protected static function calculateVariables () {
// Place variables referenced inside variables
// Excecute any custom functions
foreach ( self::$storage->variables as $name => &$value ) {
-
// Referenced variables
$value = preg_replace_callback(
$regex->function->var, array( 'self', 'cb_placeVariables' ), $value );
- // Custom functions
- $value = CssCrush_Function::parseAndExecuteValue( $value );
+ // Custom functions:
+ // Variable values can be escaped from function parsing with a double bang
+ if ( strpos( $value, '!!' ) === 0 ) {
+ $value = ltrim( $value, "!\t\r " );
+ }
+ else {
+ $value = csscrush_function::parseAndExecuteValue( $value );
+ }
}
}
@@ -633,19 +679,23 @@ protected static function placeVariables ( $stream ) {
protected static function reset () {
// Reset properties for current process
self::$tokenUID = 0;
- self::$storage = new stdClass;
+ 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;
}
- protected static function compile ( $stream, $preprocessed = false ) {
+ protected static function compile ( $stream ) {
$regex = self::$regex;
+ $options = self::$options;
// Load in aliases and macros
if ( !self::$assetsLoaded ) {
@@ -657,14 +707,6 @@ protected static function compile ( $stream, $preprocessed = false ) {
self::$aliases = self::$aliasesRaw;
self::pruneAliases();
- if ( !$preprocessed ) {
- // Extract comments
- $stream = self::extractComments( $stream );
- }
-
- // Extract strings
- $stream = self::extractStrings( $stream );
-
// Parse variables
$stream = self::extractVariables( $stream );
@@ -676,7 +718,7 @@ protected static function compile ( $stream, $preprocessed = false ) {
$stream = self::placeVariables( $stream );
// Normalize whitespace
- $stream = self::normalize( $stream );
+ $stream = csscrush_util::normalizeWhiteSpace( $stream );
// Adjust the stream so we can extract the rules cleanly
$map = array(
@@ -696,6 +738,11 @@ protected static function compile ( $stream, $preprocessed = false ) {
// print it all back
$stream = self::display( $stream );
+ // Add in boilerplate
+ if ( $options[ 'boilerplate' ] ) {
+ $stream = self::getBoilerplate() . "\n$stream";
+ }
+
self::log( self::$config->data );
// Release memory
@@ -837,17 +884,8 @@ protected static function minify ( $str ) {
array_keys( $replacements ), array_values( $replacements ), $str );
}
- protected static function normalize ( $str ) {
- $replacements = array(
- '!\s+!' => ' ',
- '!(\[)\s*|\s*(\])|(\()\s*|\s*(\))!' => '${1}${2}${3}${4}', // Trim internal bracket WS
- '!\s*(;|,|\/|\!)\s*!' => '$1', // Trim WS around delimiters and special characters
- );
- return preg_replace(
- array_keys( $replacements ), array_values( $replacements ), $str );
- }
-
protected static function aliasAtRules ( $stream ) {
+
if ( empty( self::$aliases[ 'at-rules' ] ) ) {
return $stream;
}
@@ -870,7 +908,7 @@ protected static function aliasAtRules ( $stream ) {
// Store the match position
$block_start_pos = $match[0][1];
// Capture the curly bracketed block
- $curly_match = self::matchBrackets( $stream, $brackets = array( '{', '}' ), $block_start_pos );
+ $curly_match = csscrush_util::matchBrackets( $stream, $brackets = array( '{', '}' ), $block_start_pos );
if ( !$curly_match ) {
// Couldn't match the block
@@ -943,25 +981,38 @@ protected static function aliasAtRules ( $stream ) {
return $stream;
}
+ public static function createTokenLabel ( $prefix, $counter = null ) {
+ $counter = !is_null( $counter ) ? $counter : ++self::$tokenUID;
+ return "___$prefix{$counter}___";
+ }
+
#############################
# preg_replace callbacks
protected static function cb_extractStrings ( $match ) {
- $label = CssCrush::createTokenLabel( 's' );
- CssCrush::$storage->tokens->strings[ $label ] = $match[0];
+ $label = csscrush::createTokenLabel( 's' );
+ csscrush::$storage->tokens->strings[ $label ] = $match[0];
return $label;
}
protected static function cb_restoreStrings ( $match ) {
- return CssCrush::$storage->tokens->strings[ $match[0] ];
+ return csscrush::$storage->tokens->strings[ $match[0] ];
}
protected static function cb_extractComments ( $match ) {
+
$comment = $match[0];
- $flagged = strpos( $comment, '/*!' ) === 0;
+
+ // Strip private comments
+ $private_comment_marker = '$!';
+ if ( strpos( $comment, '/*' . $private_comment_marker ) === 0 ) {
+ return '';
+ }
+
$label = self::createTokenLabel( 'c' );
- self::$storage->tokens->comments[ $label ] = $flagged ? '/*!' . substr( $match[1], 1 ) . '*/' : $comment;
+ self::$storage->tokens->comments[ $label ] = $comment;
+
return $label;
}
@@ -978,7 +1029,7 @@ protected static function cb_extractVariables ( $match ) {
$block = preg_replace( $regex->token->comment, '', $block );
// Need to split safely as there are semi-colons in data-uris
- $variables_match = self::splitDelimList( $block, ';', true );
+ $variables_match = csscrush_util::splitDelimList( $block, ';', true );
// Loop through the pairs, restore parens
foreach ( $variables_match->list as $var ) {
@@ -1004,7 +1055,6 @@ protected static function cb_placeVariables ( $match ) {
$variable_name = $match[2];
}
- //self::log( $match );
if ( isset( self::$storage->variables[ $variable_name ] ) ) {
return $before_char . self::$storage->variables[ $variable_name ];
}
@@ -1019,26 +1069,26 @@ protected static function cb_extractAndProcessRules ( $match ) {
$rule->selector_raw = $match[1];
$rule->declaration_raw = $match[2];
- CssCrush_Hook::run( 'rule_preprocess', $rule );
+ csscrush_hook::run( 'rule_preprocess', $rule );
- $rule = new CssCrush_Rule( $rule->selector_raw, $rule->declaration_raw );
+ $rule = new csscrush_rule( $rule->selector_raw, $rule->declaration_raw );
// Only store rules with declarations
if ( !empty( $rule->declarations ) ) {
- CssCrush_Hook::run( 'rule_prealias', $rule );
+ csscrush_hook::run( 'rule_prealias', $rule );
if ( !empty( self::$aliases ) ) {
$rule->addPropertyAliases();
$rule->addFunctionAliases();
$rule->addValueAliases();
}
- CssCrush_Hook::run( 'rule_postalias', $rule );
+ csscrush_hook::run( 'rule_postalias', $rule );
$rule->expandSelectors();
- CssCrush_Hook::run( 'rule_postprocess', $rule );
+ csscrush_hook::run( 'rule_postprocess', $rule );
$label = self::createTokenLabel( 'r' );
self::$storage->tokens->rules[ $label ] = $rule;
@@ -1084,6 +1134,7 @@ protected static function cb_printRule ( $match ) {
}
}
+
############
# Parsing methods
@@ -1103,186 +1154,29 @@ public static function extractStrings ( $stream ) {
return preg_replace_callback( self::$regex->string, array( 'self', 'cb_extractStrings' ), $stream );
}
-
- ############
- # Utilities
-
- 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 ) ) {
- $match_obj->list = explode( $delim, $match_obj->string );
- }
- else {
- $match_obj->list = preg_split( '!' . $delim . '!', $match_obj->string );
- }
-
- if ( false === $allow_empty ) {
- $match_obj->list = array_filter( $match_obj->list );
- }
- if ( $fold_in ) {
- $match_keys = array_keys( $match_obj->matches );
- $match_values = array_values( $match_obj->matches );
- foreach ( $match_obj->list as &$item ) {
- $item = str_replace( $match_keys, $match_values, $item );
- }
- }
- return $match_obj;
- }
-
- public static function createTokenLabel ( $prefix, $counter = null ) {
- $counter = !is_null( $counter ) ? $counter : ++self::$tokenUID;
- return "___$prefix{$counter}___";
- }
-
- public static function matchBrackets ( $str, $brackets = array( '(', ')' ), $search_pos = 0 ) {
-
- list( $opener, $closer ) = $brackets;
- $openings = array();
- $closings = array();
- $brake = 50; // Set a limit in the case of errors
-
- $match = new stdClass;
-
- $start_index = strpos( $str, $opener, $search_pos );
- $close_index = strpos( $str, $closer, $search_pos );
-
- if ( $start_index === false ) {
- return false;
- }
- if ( substr_count( $str, $opener ) !== substr_count( $str, $closer ) ) {
- $sample = substr( $str, 0, 15 );
- trigger_error( __METHOD__ . ": Unmatched token near '$sample'.\n", E_USER_WARNING );
- return false;
- }
-
- while (
- ( $start_index !== false or $close_index !== false ) and $brake--
- ) {
- if ( $start_index !== false and $close_index !== false ) {
- $search_pos = min( $start_index, $close_index );
- if ( $start_index < $close_index ) {
- $openings[] = $start_index;
- }
- else {
- $closings[] = $close_index;
- }
- }
- elseif ( $start_index !== false ) {
- $search_pos = $start_index;
- $openings[] = $start_index;
- }
- else {
- $search_pos = $close_index;
- $closings[] = $close_index;
- }
- $search_pos += 1; // Advance
-
- if ( count( $closings ) === count( $openings ) ) {
- $match->openings = $openings;
- $match->closings = $closings;
- $match->start = $openings[0];
- $match->end = $closings[ count( $closings ) - 1 ] + 1;
- return $match;
- }
- $start_index = strpos( $str, $opener, $search_pos );
- $close_index = strpos( $str, $closer, $search_pos );
- }
-
- trigger_error( __METHOD__ . ": Reached brake limit of '$brake'. Exiting.\n", E_USER_WARNING );
- return false;
- }
-
- public static function matchAllBrackets ( $str, $pair = '()', $offset = 0 ) {
-
- $match_obj = new stdClass;
- $match_obj->string = $str;
- $match_obj->raw = $str;
- $match_obj->matches = array();
-
- list( $opener, $closer ) = str_split( $pair, 1 );
-
- // Return early if there's no match
- if ( false === ( $first_offset = strpos( $str, $opener, $offset ) ) ) {
- return $match_obj;
- }
-
- // Step through the string one character at a time storing offsets
- $paren_score = -1;
- $inside_paren = false;
- $match_start = 0;
- $offsets = array();
-
- for ( $index = $first_offset; $index < strlen( $str ); $index++ ) {
- $char = $str[ $index ];
-
- if ( $opener === $char ) {
- if ( !$inside_paren ) {
- $paren_score = 1;
- $match_start = $index;
- }
- else {
- $paren_score++;
- }
- $inside_paren = true;
- }
- elseif ( $closer === $char ) {
- $paren_score--;
- }
-
- if ( 0 === $paren_score ) {
- $inside_paren = false;
- $paren_score = -1;
- $offsets[] = array( $match_start, $index + 1 );
- }
- }
-
- // Step backwards through the matches
- while ( $offset = array_pop( $offsets ) ) {
- list( $start, $finish ) = $offset;
-
- $before = substr( $str, 0, $start );
- $content = substr( $str, $start, $finish - $start );
- $after = substr( $str, $finish );
-
- $label = self::createTokenLabel( 'p' );
- $str = $before . $label . $after;
- $match_obj->matches[ $label ] = $content;
-
- // Parens will be folded in later
- self::$storage->tokens->parens[ $label ] = $content;
- }
-
- $match_obj->string = $str;
-
- return $match_obj;
- }
-
}
#######################
# Procedural style API
function csscrush_file ( $file, $options = null ) {
- return CssCrush::file( $file, $options );
+ return csscrush::file( $file, $options );
}
function csscrush_tag ( $file, $options = null, $attributes = array() ) {
- return CssCrush::tag( $file, $options, $attributes );
+ return csscrush::tag( $file, $options, $attributes );
}
function csscrush_inline ( $file, $options = null, $attributes = array() ) {
- return CssCrush::inline( $file, $options, $attributes );
+ return csscrush::inline( $file, $options, $attributes );
}
function csscrush_string ( $string, $options = null ) {
- return CssCrush::string( $string, $options );
+ return csscrush::string( $string, $options );
}
function csscrush_globalvars ( $vars ) {
- return CssCrush::globalVars( $vars );
+ return csscrush::globalVars( $vars );
}
function csscrush_clearcache ( $dir = '' ) {
- return CssCrush::clearcache( $dir );
+ return csscrush::clearcache( $dir );
}
View
72 lib/Function.php
@@ -5,7 +5,7 @@
*
*/
-class CssCrush_Function {
+class csscrush_function {
// Regex pattern for finding custom functions
public static $functionPatt;
@@ -135,7 +135,7 @@ protected static function parseMathArgs ( $input ) {
}
protected static function parseArgs ( $input, $allowSpaceDelim = false ) {
- $args = CssCrush::splitDelimList( $input,
+ $args = csscrush_util::splitDelimList( $input,
( $allowSpaceDelim ? '\s*[,\s]\s*' : ',' ),
true, true );
return array_map( 'trim', $args->list );
@@ -144,7 +144,7 @@ protected static function parseArgs ( $input, $allowSpaceDelim = false ) {
protected static function colorAdjust ( $color, array $adjustments ) {
$fn_matched = preg_match( '!^(#|rgba?|hsla?)!', $color, $m );
- $keywords = CssCrush_Color::getKeywords();
+ $keywords = csscrush_color::getKeywords();
// Support for Hex, RGB, RGBa and keywords
// HSL and HSLa are passed over
@@ -157,7 +157,7 @@ protected static function colorAdjust ( $color, array $adjustments ) {
if ( $fn_matched ) {
switch ( $m[1] ) {
case '#':
- $rgb = CssCrush_Color::hexToRgb( $color );
+ $rgb = csscrush_color::hexToRgb( $color );
break;
case 'rgb':
@@ -173,10 +173,10 @@ protected static function colorAdjust ( $color, array $adjustments ) {
$alpha = array_pop( $vals );
}
if ( 0 === strpos( $function, 'rgb' ) ) {
- $rgb = CssCrush_Color::normalizeCssRgb( $vals );
+ $rgb = csscrush_color::normalizeCssRgb( $vals );
}
else {
- $rgb = CssCrush_Color::cssHslToRgb( $vals );
+ $rgb = csscrush_color::cssHslToRgb( $vals );
}
break;
}
@@ -185,7 +185,7 @@ protected static function colorAdjust ( $color, array $adjustments ) {
$rgb = $keywords[ $color ];
}
- $hsl = CssCrush_Color::rgbToHsl( $rgb );
+ $hsl = csscrush_color::rgbToHsl( $rgb );
// Normalize adjustment parameters to floating point numbers
// then calculate the new HSL value
@@ -213,12 +213,12 @@ protected static function colorAdjust ( $color, array $adjustments ) {
}
// Finally convert new HSL value to RGB
- $rgb = CssCrush_Color::hslToRgb( $hsl );
+ $rgb = csscrush_color::hslToRgb( $hsl );
// Return as hex if there is no modified alpha channel
// Otherwise return RGBA string
if ( 1 === $alpha ) {
- return CssCrush_Color::rgbToHex( $rgb );
+ return csscrush_color::rgbToHex( $rgb );
}
$rgb[] = $alpha;
return 'rgba(' . implode( ',', $rgb ) . ')';
@@ -234,31 +234,48 @@ protected static function colorAdjust ( $color, array $adjustments ) {
public static function css_fn__math ( $input ) {
// Whitelist allowed characters
$input = preg_replace( '![^\.0-9\*\/\+\-\(\)]!', '', $input );
- $result = 0;
- try {
- $result = eval( "return $input;" );
- }
- catch ( Exception $e ) {};
- return round( $result, 10 );
+
+ $result = @eval( "return $input;" );
+
+ return $result === false ? 0 : round( $result, 10 );
}
public static function css_fn__percent ( $input ) {
$args = self::parseMathArgs( $input );
- // Use precision argument if it exists, default to 7
- $precision = isset( $args[2] ) ? $args[2] : 7;
+ // Use precision argument if it exists, use default otherwise
+ $precision = isset( $args[2] ) ? $args[2] : 5;
+ // Output zero on failure
$result = 0;
- if ( count( $args ) > 1 ) {
+
+ // Need to check arguments or we may see divide by zero errors
+ if ( count( $args ) > 1 and !empty( $args[0] ) and !empty( $args[1] ) ) {
+
+ // Use bcmath if it's available for higher precision
+
// Arbitary high precision division
- $div = (string) bcdiv( $args[0], $args[1], 25 );
+ if ( function_exists( 'bcdiv' ) ) {
+ $div = bcdiv( $args[0], $args[1], 25 );
+ }
+ else {
+ $div = $args[0] / $args[1];
+ }
+
// Set precision percentage value
- $result = (string) bcmul( $div, '100', $precision );
+ if ( function_exists( 'bcmul' ) ) {
+ $result = bcmul( (string) $div, '100', $precision );
+ }
+ else {
+ $result = round( $div * 100, $precision );
+ }
+
// Trim unnecessary zeros and decimals
- $result = trim( $result, '0' );
+ $result = trim( (string) $result, '0' );
$result = rtrim( $result, '.' );
}
+
return $result . '%';
}
@@ -271,28 +288,25 @@ public static function css_fn__data_uri ( $input ) {
// Normalize, since argument might be a string token
if ( strpos( $input, '___s' ) === 0 ) {
- $string_labels = array_keys( CssCrush::$storage->tokens->strings );
- $string_values = array_values( CssCrush::$storage->tokens->strings );
+ $string_labels = array_keys( csscrush::$storage->tokens->strings );
+ $string_values = array_values( csscrush::$storage->tokens->strings );
$input = trim( str_replace( $string_labels, $string_values, $input ), '\'"`' );
}
// Default return value
$result = "url($input)";
// No attempt to process absolute urls
- if (
- strpos( $input, 'http://' ) === 0 or
- strpos( $input, 'https://' ) === 0
- ) {
+ if ( preg_match( csscrush::$regex->absoluteUrl, $input ) ) {
return $result;
}
// Get system file path
if ( strpos( $input, '/' ) === 0 ) {
- $file = CssCrush::$config->docRoot . $input;
+ $file = csscrush::$config->docRoot . $input;
}
else {
- $baseDir = CssCrush::$config->baseDir;
+ $baseDir = csscrush::$config->baseDir;
$file = "$baseDir/$input";
}
View
3 lib/Hook.php
@@ -4,7 +4,8 @@
* Access to the execution flow for plugins
*
*/
-class CssCrush_Hook {
+
+class csscrush_hook {
static public $record = array();
View
303 lib/Importer.php
@@ -5,14 +5,21 @@
*
*/
-class CssCrush_Importer {
+class csscrush_importer {
+
public static function save ( $data ) {
- $config = CssCrush::$config;
+ $config = csscrush::$config;
+ $options = csscrush::$options;
+
+ // No saving if caching is disabled, return early
+ if ( ! $options[ 'cache' ] ) {
+ return;
+ }
// Write to config
- $config->data[ CssCrush::$compileName ] = $data;
+ $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;
@@ -21,77 +28,104 @@ public static function save ( $data ) {
file_put_contents( $config->path, serialize( $config->data ) );
}
+
public static function hostfile ( $hostfile ) {
- $config = CssCrush::$config;
- $regex = CssCrush::$regex;
+ $config = csscrush::$config;
+ $options = csscrush::$options;
+ $regex = csscrush::$regex;
// Keep track of all import file info for later logging
$mtimes = array();
$filenames = array();
- // Get the hostfile with comments extracted
- $str = CssCrush::extractComments( file_get_contents( $hostfile->path ) );
+ // Determine input; string or file
+ // Extract the comments then strings
+ $stream = isset( $hostfile->string ) ? $hostfile->string : file_get_contents( $hostfile->path );
- // This may be set non-zero if an absolute URL is encountered
+ // If there's a prepend file, prepend it
+ if ( $prependFile = csscrush_util::find( 'Prepend-local.css', 'Prepend.css' ) ) {
+ $stream = file_get_contents( $prependFile ) . $stream;
+ }
+
+ $stream = csscrush::extractComments( $stream );
+ $stream = csscrush::extractStrings( $stream );
+
+ // This may be set non-zero during the script if an absolute URL is encountered
$searchOffset = 0;
// Recurses until the nesting heirarchy is flattened and all files are combined
- while ( preg_match( $regex->import, $str, $match, PREG_OFFSET_CAPTURE, $searchOffset ) ) {
+ while ( preg_match( $regex->import, $stream, $match, PREG_OFFSET_CAPTURE, $searchOffset ) ) {
$fullMatch = $match[0][0]; // Full match
$matchStart = $match[0][1]; // Full match offset
$matchEnd = $matchStart + strlen( $fullMatch );
- $url = trim( $match[1][0] ); // The url
$mediaContext = trim( $match[2][0] ); // The media context if specified
- $preStatement = substr( $str, 0, $matchStart );
- $postStatement = substr( $str, $matchEnd );
+ $preStatement = substr( $stream, 0, $matchStart );
+ $postStatement = substr( $stream, $matchEnd );
+
+ $url = trim( $match[1][0] );
+
+ // Url may be a string token
+ if ( preg_match( $regex->token->string, $url ) ) {
+ $import_url_token = new csscrush_string( $url );
+ $url = $import_url_token->value;
+ // $import_url_token = csscrush::$storage->tokens->strings[ $url ];
+ // $url = trim( $import_url_token, '\'"' );
+ }
+
+ // csscrush::log( $url );
// Pass over absolute urls
// Move the search pointer forward
- if ( preg_match( '!^https?://!', $url ) ) {
+ if ( preg_match( $regex->absoluteUrl, $url ) ) {
$searchOffset = $matchEnd;
continue;
}
+ // Create import object
$import = new stdClass;
- $import->name = $url;
+ $import->url = $url;
$import->mediaContext = $mediaContext;
+ $import->hostDir = $hostfile->dir;
// Check to see if the url is root relative
- if ( strpos( $import->name, '/' ) === 0 ) {
- $import->path = $config->docRoot . $import->name;
+ // Flatten import path for convenience
+ if ( strpos( $import->url, '/' ) === 0 ) {
+ $import->path = realpath( $config->docRoot . $import->url );
}
else {
- $import->path = "$hostfile->dir/$import->name";
+ $import->path = realpath( "$hostfile->dir/$import->url" );
}
$import->content = @file_get_contents( $import->path );
// Failed to open import, just continue with the import line removed
- if ( !$import->content ) {
- CssCrush::log( "Import file '$import->name' not found" );
- $str = $preStatement . $postStatement;
+ if ( ! $import->content ) {
+ csscrush::log( "Import file '$import->url' not found" );
+ $stream = $preStatement . $postStatement;
continue;
}
- // Import file opened successfully so we process it
- // We need to resolve relative urls in all imported files since they will be brought inline with the hostfile
+ // 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 {
// Start with extracting comments in the import
- $import->content = CssCrush::extractComments( $import->content );
+ $import->content = csscrush::extractComments( $import->content );
- $import->dir = dirname( $import->name );
+ $import->dir = dirname( $import->url );
- // Store import file info
+ // Store import file info for cache validation
$mtimes[] = filemtime( $import->path );
- $filenames[] = $import->name;
+ $filenames[] = $import->url;
- // Match all @import statements in the import content
- // Alter all the url strings to be paths relative to the hostfile
- $matchCount = preg_match_all( $regex->import, $import->content, $matchAll, PREG_OFFSET_CAPTURE );
- // Store the replacements we might find
+ // Alter all the url strings to be paths relative to the 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 );
$replacements = array();
for ( $index = 0; $index < $matchCount; $index++ ) {
@@ -105,42 +139,22 @@ public static function hostfile ( $hostfile ) {
// On failure strip the @import statement
if ( strpos( $urlMatch, '/' ) === 0 ) {
$replace = self::resolveAbsolutePath( $urlMatch );
- if ( !$replace ) {
+ if ( ! $replace ) {
$search = $fullMatch;
$replace = '';
}
}
+ // Trim the statement and set the resolved path
$statement = trim( str_replace( $search, $replace, $fullMatch ) );
- // TODO: Normalise import statement to be without url() syntax
- //
- // $patt = '!^@import\s+url\(\s*[\'"]?!';
- // if ( preg_match( $patt, $statement ) ) {
- //
- // // @import url( "some_path_with_(parens).css") screen and ( max-width: 500px );
- // // @import url( some_path_with_(parens).css );
- //
- // $statement = preg_replace( '!^@import\s+url\(\s*!', '', $statement );
- //
- // // 'some_path_with_(parens).css') screen and ( max-width: 500px );
- // // some_path_with_(parens).css) screen and ( max-width: 500px );
- //
- // // A url surrounded in quotes
- // // if ( preg_match( '!^([\'"])!', $statement, $m ) ) {
- // // if ( ( $closing_quote_index = strpos( $statement, $m[1], 1 ) ) === false ) {
- // // // Mismatched quote
- // // }
- // // $closing_quote_index;
- // //
- // // }
- // // else {
- // //
- // // }
- // }
-
+ // 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 we've stored any altered @import strings then we need to apply them
if ( count( $replacements ) ) {
$import->content = str_replace(
@@ -149,36 +163,191 @@ public static function hostfile ( $hostfile ) {
$import->content );
}
- // TODO: Optionally resolve relative url and custom function data-uri references
+ // 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 );
+ }
// Add media context if it exists
if ( $import->mediaContext ) {
$import->content = "@media $import->mediaContext {" . $import->content . '}';
}
- $str = $preStatement . $import->content . $postStatement;
+ $stream = $preStatement . $import->content . $postStatement;
}
} // End while
- self::save( array(
- 'imports' => $filenames,
- 'datem_sum' => array_sum( $mtimes ) + $hostfile->mtime,
- 'options' => CssCrush::$options,
- ));
+ // Save only if the hostfile object is associated with a real file
+ if ( $hostfile->path ) {
+ self::save( array(
+ 'imports' => $filenames,
+ 'datem_sum' => array_sum( $mtimes ) + $hostfile->mtime,
+ 'options' => $options,
+ ));
+ }
+
+ return $stream;
+ }
+
+
+ protected static function normalizeImportStatement ( $statement ) {
+
+ $url_import_patt = '!^@import\s+url\(\s*!';
+ if ( preg_match( $url_import_patt, $statement ) ) {
+ // Example matches:
+ // @import url( "some_path_with_(parens).css") screen and ( max-width: 500px );
+ // @import url( some_path.css );
- return $str;
+ // Trim the first part
+ $statement = preg_replace( $url_import_patt, '', $statement );
+
+ // 'some_path_with_(parens).css') screen and ( max-width: 500px );
+ if ( preg_match( '!^([\'"])!', $statement, $m ) ) {
+ $statement = preg_replace( '!' . $m[1] . '\s*\)!', $m[1], $statement, 1 );
+ }
+ // some_path.css) screen and ( max-width: 500px );
+ else {
+ $statement = '"' . preg_replace( '!\s*\)!', '"', $statement, 1 );
+ }
+ // Pull back together
+ $statement = '@import ' . $statement;
+ }
+ return $statement;
}
+
protected static function resolveAbsolutePath ( $url ) {
- $config = CssCrush::$config;
- if ( !file_exists ( $config->docRoot . $url ) ) {
+ $config = csscrush::$config;
+
+ 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 );
+
return $url;
}
+
+ protected static function rewriteImportRelativeUrls ( $import ) {
+
+ $stream = $import->content;
+
+ // We're comparing file system position so we'll
+ $hostDir = csscrush_util::normalizeSystemPath( $import->hostDir, true );
+ $importDir = csscrush_util::normalizeSystemPath( dirname( $import->path ), true );
+
+ csscrush::$storage->tmp->relativeUrlPrefix = '';
+ $url_prefix = '';
+
+ if ( $importDir === $hostDir ) {
+ // Do nothing if files are in the same directory
+ return $stream;
+
+ }
+ elseif ( strpos( $importDir, $hostDir ) === false ) {
+ // Import directory is higher than the host directory
+
+ // Split the directory paths into arrays so we can compare segment by segment
+ $host_segs = preg_split( '!/+!', $hostDir, null, PREG_SPLIT_NO_EMPTY );
+ $import_segs = preg_split( '!/+!', $importDir, null, PREG_SPLIT_NO_EMPTY );
+
+ // Shift the segments until they are on different branches
+ while ( @( $host_segs[0] == $import_segs[0] ) ) {
+ array_shift( $host_segs );
+ array_shift( $import_segs );
+ // csscrush::log( array( $host_segs, $import_segs ) );
+ }
+
+ // Count the remaining $host_segs to get the offset
+ $level_diff = count( $host_segs );
+
+ $url_prefix = str_repeat( '../', $level_diff ) . implode( '/', $import_segs );
+
+ }
+ else {
+ // Import directory is lower than host directory
+
+ // easy, url_prefix is the difference
+ $url_prefix = substr( $importDir, strlen( $hostDir ) + 1 );
+ }
+
+ if ( empty( $url_prefix ) ) {
+ return $stream;
+ }
+
+ // Add the directory seperator ending (if needed)
+ if ( $url_prefix[ strlen( $url_prefix ) - 1 ] !== '/' ) {
+ $url_prefix .= '/';
+ }
+
+ csscrush::log( 'relative_url_prefix: ' . $url_prefix );
+
+ // Search for all relative url and data-uri references in the content
+ // and prepend $relative_url_prefix
+
+ // Make $url_prefix accessible in callback scope
+ csscrush::$storage->tmp->relativeUrlPrefix = $url_prefix;
+
+ $url_function_patt = '!
+ ([^a-z-]) # the preceeding character
+ (data-uri|url) # the function name
+ \(\s*([^\)]+)\s*\) # the url
+ !xi';
+ $stream = preg_replace_callback( $url_function_patt,
+ array( 'self', 'cb_rewriteImportRelativeUrl' ), $stream );
+
+ return $stream;
+ }
+
+
+ protected static function cb_rewriteImportRelativeUrl ( $match ) {
+
+ $regex = csscrush::$regex;
+ $storage = csscrush::$storage;
+
+ // The relative url prefix
+ $relative_url_prefix = $storage->tmp->relativeUrlPrefix;
+
+ list( $fullMatch, $before, $function, $url ) = $match;
+ $url = trim( $url );
+
+ // If the url is a string token we'll need to restore it as a string token later
+ if ( $url_is_token = preg_match( $regex->token->string, $url ) ) {
+
+ $url_token = new csscrush_string( $url );
+ $url = $url_token->value;
+ }
+
+ // No rewrite if:
+ // $url begins with a variable, e.g '$('
+ // $url path is absolute or begins with slash
+ // $url is an empty string
+ if (
+ empty( $url ) or
+ strpos( $url, '/' ) === 0 or
+ strpos( $url, '$(' ) === 0 or
+ preg_match( $regex->absoluteUrl, $url )
+ ) {
+ // Token or not, it's ok to return the full match if $url is a root relative or absolute ref
+ return $fullMatch;
+ }
+
+ // Prepend the relative url prefix
+ $url = $relative_url_prefix . $url;
+
+ // Restore quotes if $url was a string token
+ if ( $url_is_token ) {
+ $url = $url_token->quoteMark . $url . $url_token->quoteMark;
+ }
+
+ // Reconstruct the match and return
+ return "$before$function($url)";
+ }
+
}
View
22 lib/Rule.php
@@ -5,7 +5,7 @@
*
*/
-class CssCrush_Rule implements IteratorAggregate {
+class csscrush_rule implements IteratorAggregate {
public $vendorContext = null;
public $properties = array();
@@ -16,12 +16,12 @@ class CssCrush_Rule implements IteratorAggregate {
public function __construct ( $selector_string = null, $declarations_string ) {
- $regex = CssCrush::$regex;
+ $regex = csscrush::$regex;
// Parse the selectors chunk
if ( !empty( $selector_string ) ) {
- $selectors_match = CssCrush::splitDelimList( $selector_string, ',' );
+ $selectors_match = csscrush_util::splitDelimList( $selector_string, ',' );
$this->parens += $selectors_match->matches;
// Remove and store comments that sit above the first selector
@@ -36,11 +36,11 @@ public function __construct ( $selector_string = null, $declarations_string ) {
}
// Apply any custom functions
- $declarations_string = CssCrush_Function::parseAndExecuteValue( $declarations_string );
+ $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::splitDelimList( $declarations_string, ';' );
+ $declarations_match = csscrush_util::splitDelimList( $declarations_string, ';' );
$this->parens += $declarations_match->matches;
// Parse declarations in to property/value pairs
@@ -86,7 +86,7 @@ public function __construct ( $selector_string = null, $declarations_string ) {
// Create an index of all functions in the current declaration
if ( preg_match_all( $regex->function->match, $value, $functions ) > 0 ) {
- // CssCrush::log( $functions );
+ // csscrush::log( $functions );
$out = array();
foreach ( $functions[2] as $index => $fn_name ) {
$out[] = $fn_name;
@@ -112,8 +112,8 @@ public function __construct ( $selector_string = null, $declarations_string ) {
public function addPropertyAliases () {
- $regex = CssCrush::$regex;
- $aliasedProperties =& CssCrush::$aliases[ 'properties' ];
+ $regex = csscrush::$regex;
+ $aliasedProperties =& csscrush::$aliases[ 'properties' ];
// First test for the existence of any aliased properties