Skip to content

Instantly share code, notes, and snippets.

@StanAngeloff
Created July 23, 2012 16:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save StanAngeloff/3164569 to your computer and use it in GitHub Desktop.
Save StanAngeloff/3164569 to your computer and use it in GitHub Desktop.
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
Copy link

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
Copy link

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