sassy.module in Sassy 7
Same filename and directory in other branches
Handles compiling of .sass / .scss files.
The theme system allows for nearly all output of the Drupal system to be customized by user themes.
File
sassy.moduleView source
<?php
/**
* @file
* Handles compiling of .sass / .scss files.
*
* The theme system allows for nearly all output of the Drupal system to be
* customized by user themes.
*/
/**
* Implementation of hook_flush_caches().
*/
function sassy_flush_caches() {
sassy_clear_cache();
}
/**
* Implementation of hook_element_info_alter().
*/
function sassy_element_info_alter(&$type) {
array_unshift($type['styles']['#pre_render'], 'sassy_pre_render');
}
/**
* Implementation of hook_form_FORM_ID_alter().
*/
function sassy_form_system_performance_settings_alter(&$form, &$form_states) {
$form['sassy'] = array(
'#type' => 'fieldset',
'#title' => t('Development settings for the SASSY module'),
);
$form['sassy']['sassy_devel'] = array(
'#type' => 'checkbox',
'#title' => t('Recompile all SASS / SCSS files on every page request.'),
'#description' => t('Disables the caching of SASS / SCSS files. Useful for when regularly changing the stylesheets (during theme development).'),
'#default_value' => variable_get('sassy_devel', FALSE),
);
}
/**
* Builds the SASS cache. Should only be invoked by drupal_render().
*
* @param $elements
* A render array containing:
* '#items': The CSS items as returned by drupal_add_css() and altered by
* drupal_get_css().
* '#group_callback': A function to call to group #items to enable the use of
* fewer tags by aggregating files and/or using multiple @import statements
* within a single tag.
* '#aggregate_callback': A function to call to aggregate the items within the
* groups arranged by the #group_callback function.
*
* @return $elements
* The modified (pre-rendered) $elements parameter.
*/
function sassy_pre_render($elements) {
$devel = variable_get('sassy_devel', FALSE);
$map = $original = variable_get('sassy_cache', array());
$files = sassy_pick_files($elements['#items']);
// We can bail out here if there are no SCSS files anyways.
if (empty($files['#stylesheets']) || !module_load_include('php', 'sassy', 'phamlp/sass/SassParser')) {
// Remove the files from the array of stylesheets.
$elements['#items'] = array_diff_key($elements['#items'], $files['#stylesheets']);
return $elements;
}
foreach ($files['#stylesheets'] as $key => $file) {
// Create a unique identifier for the file.
$hash = hash('sha256', serialize($file));
$path = isset($map[$hash]) ? $map[$hash] : NULL;
// We recompile this file if recompile equals TRUE, array (and thereby the
// hash value) changed, if the file doesn't exist, or if we are in development
// mode. NOTE: You can use the 'recompile' array for your CSS files to cache
// them based on advanced criteria.
if ($devel || $file['recompile'] === TRUE || !isset($map[$hash]) || !file_exists($path)) {
if (!isset($includes[$file['syntax']])) {
$includes[$file['syntax']] = !empty($files['#includes'][$file['syntax']]) ? implode("\n\n", array_map('sassy_load_stylesheet', $files['#includes'][$file['syntax']])) : '';
}
$data = sassy_load_stylesheet($file['data']);
$output = sassy_parse($file, $includes[$file['syntax']] . "\n\n" . $data, $file['syntax']);
$directory = 'public://sassy';
$file['data'] = $directory . '/' . drupal_hash_base64($output) . '.css';
// Create the CSS file.
file_prepare_directory($directory, FILE_CREATE_DIRECTORY);
if (!file_exists($file['data']) && !file_unmanaged_save_data($output, $file['data'], FILE_EXISTS_REPLACE)) {
unset($elements['#items'][$key]);
continue;
}
}
// Update the item in the stylesheets array.
$elements['#items'][$key] = $file;
if ($file['recompile'] !== TRUE) {
// Add this file to the cache if it is not set to recompile on every page load.
$map[$hash] = $file['data'];
}
}
// If $map and $original don't match anymore that means we need to update the
// CSS cache.
if ($original !== $map) {
// Sort CSS items, so that they appear in the correct order.
variable_set('sassy_cache', $map);
}
return $elements;
}
/**
* Deletes old cached SCSS files.
*/
function sassy_clear_cache() {
variable_del('sassy_cache');
file_scan_directory('public://sassy', '/.*/', array(
'callback' => 'drupal_delete_file_if_stale',
));
}
/**
* Picks all SCSS and SASS files from an array of stylesheets.
*
* @param $items
* An array of stylesheets.
*
* @return
* The extracted files as an array.
*/
function sassy_pick_files(&$items) {
$files = array(
'#stylesheets' => array(),
'#includes' => array(),
);
foreach ($items as $key => $file) {
$extension = drupal_substr($file['data'], -5);
if ($file['type'] == 'file' && in_array($extension, array(
'.scss',
'.sass',
))) {
$syntax = drupal_substr($extension, -4);
if ($file['media'] == 'include') {
$files['#includes'][$syntax][$key] = $file;
unset($items[$key]);
}
else {
$file['syntax'] = $syntax;
$file['recompile'] = isset($file['recompile']) ? $file['recompile'] : FALSE;
// If the file is set to recompile on every page load then we don't want
// it to be aggregated.
$file['preprocess'] = !empty($file['recompile']) ? FALSE : $file['preprocess'];
$files['#stylesheets'][$key] = $file;
}
}
}
return $files;
}
/**
* Loads a stylesheet and writes the base path to all url declarations.
*
* @param $file
* A filepath or an array representing a stylesheet.
*
* @return
* A string that represents the processed contents of the stylesheet.
*/
function sassy_load_stylesheet($file) {
$file = is_array($file) ? $file['data'] : $file;
$data = drupal_load_stylesheet($file);
// Build the base URL of this CSS file. Start with the full URL.
$base = file_create_url($file);
// Move to the parent.
$base = substr($base, 0, strrpos($base, '/'));
// Simplify to a relative URL if the stylesheet URL starts with the base URL
// of the website.
if (substr($base, 0, strlen($GLOBALS['base_root'])) == $GLOBALS['base_root']) {
$base = substr($base, strlen($GLOBALS['base_root']));
}
_drupal_build_css_path(NULL, $base . '/');
// Anchor all paths in the CSS with its base URL, ignoring external and
// absolute paths.
$data = preg_replace_callback('/url\\(\\s*[\'"]?(?![a-z]+:|\\/+)([^\'")]+)[\'"]?\\s*\\)/i', '_drupal_build_css_path', $data);
$data = preg_replace("/url\\(([^'\")]+)\\)/i", "url('\$1')", $data);
return $data;
}
/**
* Parse a SCSS string and transform it into CSS.
*
* @params $data
* A SCSS string.
*
* @return
* The transformed CSS as a string.
*/
function sassy_parse($file, $data, $syntax) {
drupal_alter('sassy_pre', $data, $file, $syntax);
$placeholders = _sassy_match_media_queries($data, $syntax);
$variables = _sassy_match_variables($data, $syntax);
$data = str_replace($placeholders, array_keys($placeholders), $data);
// Quote all URLs here so PhamlP doesn't remove them.
$data = preg_replace("/url\\(([^'\")]+)\\)/i", "url('\$1')", $data);
// Execute the compiler.
$parser = new SassParser(array(
'style' => 'nested',
'cache' => FALSE,
'syntax' => $syntax,
'extensions' => array(
'compass' => array(),
),
));
$output = $parser
->toCss($data, FALSE);
$output = str_replace(array_keys($placeholders), $placeholders, $output);
$output = str_replace(array_keys($variables), $variables, $output);
drupal_alter('sassy_post', $data, $syntax);
return $output;
}
/**
* Extract SCSS variables from a SCSS string.
*
* @param $data
* A SCSS string.
*
* @return
* An array of variable values, indexed by the variable name.
*/
function _sassy_match_variables($data, $syntax) {
$variables = array();
preg_match_all('/(^|\\n)\\$([^\\s]+): (.+);/', $data, $matches);
foreach ($matches[2] as $key => $value) {
$variables['$' . $value] = $matches[3][$key];
}
return $variables;
}
/**
* Extracts all media queries from an SCSS string and replace them with named
* placeholders.
*
* @param $data
* A SCSS string.
*
* @return
* An array of placeholders values, indexed by the placeholder token.
*/
function _sassy_match_media_queries($data, $syntax) {
$placeholders = array();
preg_match_all('/@media\\s*(.+)\\s*\\{/', $data, $matches);
foreach ($matches[1] as $key => $value) {
$placeholders['sassy_media_query_' . $key] = $value;
}
return $placeholders;
}
Functions
Name | Description |
---|---|
sassy_clear_cache | Deletes old cached SCSS files. |
sassy_element_info_alter | Implementation of hook_element_info_alter(). |
sassy_flush_caches | Implementation of hook_flush_caches(). |
sassy_form_system_performance_settings_alter | Implementation of hook_form_FORM_ID_alter(). |
sassy_load_stylesheet | Loads a stylesheet and writes the base path to all url declarations. |
sassy_parse | Parse a SCSS string and transform it into CSS. |
sassy_pick_files | Picks all SCSS and SASS files from an array of stylesheets. |
sassy_pre_render | Builds the SASS cache. Should only be invoked by drupal_render(). |
_sassy_match_media_queries | Extracts all media queries from an SCSS string and replace them with named placeholders. |
_sassy_match_variables | Extract SCSS variables from a SCSS string. |