Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
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...
commit 0c51ab0cfb977c3ffd95206911207443abaf37b7 1 parent ed61165
@peteboere authored
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,40 +118,61 @@
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
@@ -161,16 +180,13 @@
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,6 +335,7 @@ 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
@@ -362,7 +343,7 @@ public static function inline ( $file, $options = null, $attributes = array() )
$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,23 +537,35 @@ 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,
);
@@ -533,6 +573,7 @@ protected static function parseOptions ( $options ) {
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,7 +679,8 @@ 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(),
@@ -641,11 +688,14 @@ protected static function reset () {
'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,14 +1069,14 @@ 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();
@@ -1034,11 +1084,11 @@ protected static function cb_extractAndProcessRules ( $match ) {
$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,163 +1154,6 @@ 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;
- }
-
}
@@ -1267,22 +1161,22 @@ public static function matchAllBrackets ( $str, $pair = '()', $offset = 0 ) {
# 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,8 +288,8 @@ 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 ), '\'"`' );
}
@@ -280,19 +297,16 @@ public static function css_fn__data_uri ( $input ) {
$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
$intersect = array_intersect( array_keys( $aliasedProperties ), array_keys( $this->properties ) );
@@ -157,7 +157,7 @@ public function addPropertyAliases () {
public function addFunctionAliases () {
- $function_aliases =& CssCrush::$aliases[ 'functions' ];
+ $function_aliases =& csscrush::$aliases[ 'functions' ];
$aliased_functions = array_keys( $function_aliases );
if ( empty( $aliased_functions ) ) {
@@ -187,7 +187,7 @@ public function addFunctionAliases () {
$new_set[] = $declaration;
continue;
}
- // CssCrush::log($intersect);
+ // csscrush::log($intersect);
// Loop the aliasable functions
foreach ( $intersect as $fn_name ) {
@@ -239,7 +239,7 @@ public function addFunctionAliases () {
public function addValueAliases () {
- $aliasedValues =& CssCrush::$aliases[ 'values' ];
+ $aliasedValues =& csscrush::$aliases[ 'values' ];
// First test for the existence of any aliased properties
$intersect = array_intersect( array_keys( $aliasedValues ), array_keys( $this->properties ) );
View
223 lib/Util.php
@@ -1,13 +1,14 @@
<?php
/**
- *
+ *
* Utilities
- *
+ *
*/
-class CssCrush_Util {
+
+class csscrush_util {
// Create html attribute string from array
- static public function attributes ( array $attributes ) {
+ public static function htmlAttributes ( array $attributes ) {
$attr_string = '';
foreach ( $attributes as $name => $value ) {
$value = htmlspecialchars( $value, ENT_COMPAT, 'UTF-8', false );
@@ -16,4 +17,216 @@ static public function attributes ( array $attributes ) {
return $attr_string;
}
-}
+
+ public static function normalizeSystemPath ( $path, $stripMsDos = false ) {
+ $path = rtrim( str_replace( '\\', '/', $path ), '/' );
+
+ if ( $stripMsDos ) {
+ $path = preg_replace( '!^[a-z]\:!i', '', $path );
+ }
+ return $path;
+ }
+
+
+ public static function find () {
+
+ foreach ( func_get_args() as $file ) {
+ $file_path = csscrush::$location . '/' . $file;
+ if ( file_exists( $file_path ) ) {
+ return $file_path;
+ }
+ }
+ return false;
+ }
+
+
+ public static function normalizeWhiteSpace ( $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 );
+ }
+
+
+ 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 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 ) ) {
+