Create a gist now

Instantly share code, notes, and snippets.

Draft PHP script to join @media queries in a CSS file.
# [..]
require 'shellwords'
on_stylesheet_saved do |filename|
script = File.dirname(__FILE__) + '/_scripts/join-media-queries.php'
system('php -q ' + Shellwords.escape(script) + ' -i ' + Shellwords.escape(filename))
end
#!/usr/bin/env php
<?php
# Prepare the environment, be strict about silly mistakes.
error_reporting(E_ALL | E_STRICT);
ini_set('display_errors', 'On');
# Try and reset the memory limit imposed on CLI scripts.
ini_set('memory_limit', '64M');
# Handy function to format and print a string to STDERR and exit with a level.
function halt($message, $level, array $options = NULL) {
file_put_contents('php://stderr', strtr($message, (array) $options) . PHP_EOL);
exit(0 << $level);
}
# Start by making sure a file was passed as an argument to this script.
if (empty ($_SERVER['argc']) || $_SERVER['argc'] <= 1) {
halt(<<<EOD
Usage: php -q :self [OPTION]... FILE
-i[SUFFIX], --in-place[=SUFFIX]
Edit files in place (makes backup if extension supplied)
EOD
, 1, array(
':self' => escapeshellarg($_SERVER['argv'][0]),
));
}
# Parse script options, e.g., '-i' for in-place updates.
$options = getopt('i::', array('in-place::'));
# The file always comes last.
$source = $_SERVER['argv'][$_SERVER['argc'] - 1];
$input = NULL;
# Accept input from STDIN.
if ($source === '-') {
$resource = fopen('php://stdin', 'rb');
while ( ! feof($resource)) {
$input = $input . fgets($resource, 4096);
}
fclose($resource);
} else {
# The source is expected to be a file...
if ( ! is_file($source)) {
halt("[E] ':source' is not a file.", 2, array(
':source' => $source,
));
}
# ...which exists on disk...
if ( ! file_exists($source)) {
halt("[E] ':source' does not exist.", 3, array(
':source' => $source,
));
}
# ...and the User has sufficient permissions to access it for reading.
if ( ! is_readable($source)) {
halt("[E] ':source' cannot be read, permission denied.", 4, array(
':source' => $source,
));
}
# Read the entire file in one go. This could take a lot of memory for larger
# buffers, but should be OK for the average sized CSS file.
$input = file_get_contents($source);
}
# Recursively descend and match all `{ .. }` blocks.
preg_match_all('#(?<media>(?:@media[^{]+)?){(?:(?:[^{}]+)|(?R))*}#s', $input, $captures);
# Loop through all matches by key.
foreach (array_keys($captures[0]) as $key) {
# If the value does not contain a @media query, drop it from the array. Or
# even if we found a @media query, make sure it was not within a string or
# a comment.
if (strpos($captures[0][$key], '@media') === FALSE || empty ($captures['media'][$key])) {
unset ($captures[0][$key]);
}
}
# Erase all @media queries from the input CSS.
foreach ($captures[0] as $query) {
$input = preg_replace('#\s*' . preg_quote($query, '#') . '#u', '', $input);
}
# Utility function to create an array key from a media query.
function sanitize_media_query_for_key($query) {
$key = trim($query);
$key = preg_replace('#[^\w\d]+#u', '-', $key);
$key = trim($key, '-');
return $key;
}
# For each @media query, group CSS code together.
$groups = array();
foreach (array_keys($captures[0]) as $key) {
$media = $captures['media'][$key];
# The code for this block starts off with the @media query in it...
$code = $captures[0][$key];
# ...which then gets stripped off...
$code = preg_replace('#' . preg_quote($media, '#') . '#u', '', $code);
# ...and any leading or trailing curly bracket also gets removed.
$code = preg_replace('#^{|}$#u', '', $code);
# If this is the first time the @media query is encountered, a new group is
# created.
$group_key = sanitize_media_query_for_key($media);
array_key_exists($group_key, $groups) OR ($groups[$group_key] = array($media, '{'));
$groups[$group_key][] = $code;
}
$result = NULL;
# Output is the input stripped of any @media queries...
$result = $result . rtrim($input);
# ...followed by those @media queries grouped together.
foreach (array_keys($groups) as $key) {
$result = $result . PHP_EOL . PHP_EOL . implode(NULL, $groups[$key]) . '}';
}
# If --in-place was specified, print result to a file.
if (array_key_exists('i', $options) || array_key_exists('in-place', $options)) {
# If we read from STDIN, assume option value is the file name.
if ($source === '-') {
$source = NULL;
}
file_put_contents($source . (isset ($options['i']) ? $options['i'] : $options['in-place']), $result);
} else {
print $result;
}
@goeko
goeko commented Aug 24, 2013

On large files we get this PHP Error.

Warning: preg_replace(): Compilation failed: regular expression is too large at offset 41501 in PATH/sass/_scripts/join-media-queries.php on line 89

These parameter changed nothing.

...
// Try and reset the memory limit imposed on CLI scripts.
ini_set('memory_limit', '512M');
// essential for huge PCREs
ini_set("pcre.backtrack_limit", "23001337");
ini_set("pcre.recursion_limit", "23001337");
// imagine your PCRE here...
...

Do you have a solution for this?

@CanRau
CanRau commented Jun 6, 2016

Nice one thank you!
How about sorting the media queries?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment