wiki.techinc.nl/includes/libs/lessc.inc.php

3797 lines
94 KiB
PHP
Raw Normal View History

Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
<?php
// @codingStandardsIgnoreFile File external to MediaWiki. Ignore coding conventions checks.
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
/**
* lessphp v0.4.0@2cc77e3c7b
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
* http://leafo.net/lessphp
*
* LESS CSS compiler, adapted from http://lesscss.org
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
*
* For ease of distribution, lessphp 0.4.0 is under a dual license.
* You are free to pick which one suits your needs.
*
* MIT LICENSE
*
* Copyright 2013, Leaf Corcoran <leafot@gmail.com>
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* GPL VERSION 3
*
* Please refer to http://www.gnu.org/licenses/gpl-3.0.html for the full
* text of the GPL version 3
*/
/**
* The LESS compiler and parser.
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
*
* Converting LESS to CSS is a three stage process. The incoming file is parsed
* by `lessc_parser` into a syntax tree, then it is compiled into another tree
* representing the CSS structure by `lessc`. The CSS tree is fed into a
* formatter, like `lessc_formatter` which then outputs CSS as a string.
*
* During the first compile, all values are *reduced*, which means that their
* types are brought to the lowest form before being dump as strings. This
* handles math equations, variable dereferences, and the like.
*
* The `parse` function of `lessc` is the entry point.
*
* In summary:
*
* The `lessc` class creates an instance of the parser, feeds it LESS code,
* then transforms the resulting tree to a CSS tree. This class also holds the
* evaluation context, such as all available mixins and variables at any given
* time.
*
* The `lessc_parser` class is only concerned with parsing its input.
*
* The `lessc_formatter` takes a CSS tree, and dumps it to a formatted string,
* handling things like indentation.
*/
class lessc {
static public $VERSION = "v0.4.0";
static public $TRUE = array("keyword", "true");
static public $FALSE = array("keyword", "false");
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
protected $libFunctions = array();
protected $registeredVars = array();
protected $preserveComments = false;
public $vPrefix = '@'; // prefix of abstract properties
public $mPrefix = '$'; // prefix of abstract blocks
public $parentSelector = '&';
public $importDisabled = false;
public $importDir = '';
protected $numberPrecision = null;
protected $allParsedFiles = array();
// set to the parser that generated the current line when compiling
// so we know how to create error messages
protected $sourceParser = null;
protected $sourceLoc = null;
static protected $nextImportId = 0; // uniquely identify imports
// attempts to find the path of an import url, returns null for css files
protected function findImport($url) {
foreach ((array)$this->importDir as $dir) {
$full = $dir.(substr($dir, -1) != '/' ? '/' : '').$url;
if ($this->fileExists($file = $full.'.less') || $this->fileExists($file = $full)) {
return $file;
}
}
return null;
}
protected function fileExists($name) {
return is_file($name);
}
static public function compressList($items, $delim) {
if (!isset($items[1]) && isset($items[0])) return $items[0];
else return array('list', $delim, $items);
}
static public function preg_quote($what) {
return preg_quote($what, '/');
}
protected function tryImport($importPath, $parentBlock, $out) {
if ($importPath[0] == "function" && $importPath[1] == "url") {
$importPath = $this->flattenList($importPath[2]);
}
$str = $this->coerceString($importPath);
if ($str === null) return false;
$url = $this->compileValue($this->lib_e($str));
// don't import if it ends in css
if (substr_compare($url, '.css', -4, 4) === 0) return false;
$realPath = $this->findImport($url);
if ($realPath === null) return false;
if ($this->importDisabled) {
return array(false, "/* import disabled */");
}
if (isset($this->allParsedFiles[realpath($realPath)])) {
return array(false, null);
}
$this->addParsedFile($realPath);
$parser = $this->makeParser($realPath);
$root = $parser->parse(file_get_contents($realPath));
// set the parents of all the block props
foreach ($root->props as $prop) {
if ($prop[0] == "block") {
$prop[1]->parent = $parentBlock;
}
}
// copy mixins into scope, set their parents
// bring blocks from import into current block
// TODO: need to mark the source parser these came from this file
foreach ($root->children as $childName => $child) {
if (isset($parentBlock->children[$childName])) {
$parentBlock->children[$childName] = array_merge(
$parentBlock->children[$childName],
$child);
} else {
$parentBlock->children[$childName] = $child;
}
}
$pi = pathinfo($realPath);
$dir = $pi["dirname"];
list($top, $bottom) = $this->sortProps($root->props, true);
$this->compileImportedProps($top, $parentBlock, $out, $parser, $dir);
return array(true, $bottom, $parser, $dir);
}
protected function compileImportedProps($props, $block, $out, $sourceParser, $importDir) {
$oldSourceParser = $this->sourceParser;
$oldImport = $this->importDir;
// TODO: this is because the importDir api is stupid
$this->importDir = (array)$this->importDir;
array_unshift($this->importDir, $importDir);
foreach ($props as $prop) {
$this->compileProp($prop, $block, $out);
}
$this->importDir = $oldImport;
$this->sourceParser = $oldSourceParser;
}
/**
* Recursively compiles a block.
*
* A block is analogous to a CSS block in most cases. A single LESS document
* is encapsulated in a block when parsed, but it does not have parent tags
* so all of it's children appear on the root level when compiled.
*
* Blocks are made up of props and children.
*
* Props are property instructions, array tuples which describe an action
* to be taken, eg. write a property, set a variable, mixin a block.
*
* The children of a block are just all the blocks that are defined within.
* This is used to look up mixins when performing a mixin.
*
* Compiling the block involves pushing a fresh environment on the stack,
* and iterating through the props, compiling each one.
*
* See lessc::compileProp()
*
*/
protected function compileBlock($block) {
switch ($block->type) {
case "root":
$this->compileRoot($block);
break;
case null:
$this->compileCSSBlock($block);
break;
case "media":
$this->compileMedia($block);
break;
case "directive":
$name = "@" . $block->name;
if (!empty($block->value)) {
$name .= " " . $this->compileValue($this->reduce($block->value));
}
$this->compileNestedBlock($block, array($name));
break;
default:
$this->throwError("unknown block type: $block->type\n");
}
}
protected function compileCSSBlock($block) {
$env = $this->pushEnv();
$selectors = $this->compileSelectors($block->tags);
$env->selectors = $this->multiplySelectors($selectors);
$out = $this->makeOutputBlock(null, $env->selectors);
$this->scope->children[] = $out;
$this->compileProps($block, $out);
$block->scope = $env; // mixins carry scope with them!
$this->popEnv();
}
protected function compileMedia($media) {
$env = $this->pushEnv($media);
$parentScope = $this->mediaParent($this->scope);
$query = $this->compileMediaQuery($this->multiplyMedia($env));
$this->scope = $this->makeOutputBlock($media->type, array($query));
$parentScope->children[] = $this->scope;
$this->compileProps($media, $this->scope);
if (count($this->scope->lines) > 0) {
$orphanSelelectors = $this->findClosestSelectors();
if (!is_null($orphanSelelectors)) {
$orphan = $this->makeOutputBlock(null, $orphanSelelectors);
$orphan->lines = $this->scope->lines;
array_unshift($this->scope->children, $orphan);
$this->scope->lines = array();
}
}
$this->scope = $this->scope->parent;
$this->popEnv();
}
protected function mediaParent($scope) {
while (!empty($scope->parent)) {
if (!empty($scope->type) && $scope->type != "media") {
break;
}
$scope = $scope->parent;
}
return $scope;
}
protected function compileNestedBlock($block, $selectors) {
$this->pushEnv($block);
$this->scope = $this->makeOutputBlock($block->type, $selectors);
$this->scope->parent->children[] = $this->scope;
$this->compileProps($block, $this->scope);
$this->scope = $this->scope->parent;
$this->popEnv();
}
protected function compileRoot($root) {
$this->pushEnv();
$this->scope = $this->makeOutputBlock($root->type);
$this->compileProps($root, $this->scope);
$this->popEnv();
}
protected function compileProps($block, $out) {
foreach ($this->sortProps($block->props) as $prop) {
$this->compileProp($prop, $block, $out);
}
$out->lines = $this->deduplicate($out->lines);
}
/**
* Deduplicate lines in a block. Comments are not deduplicated. If a
* duplicate rule is detected, the comments immediately preceding each
* occurence are consolidated.
*/
protected function deduplicate($lines) {
$unique = array();
$comments = array();
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
foreach($lines as $line) {
if (strpos($line, '/*') === 0) {
$comments[] = $line;
continue;
}
if (!in_array($line, $unique)) {
$unique[] = $line;
}
array_splice($unique, array_search($line, $unique), 0, $comments);
$comments = array();
}
return array_merge($unique, $comments);
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
}
protected function sortProps($props, $split = false) {
$vars = array();
$imports = array();
$other = array();
$stack = array();
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
foreach ($props as $prop) {
switch ($prop[0]) {
case "comment":
$stack[] = $prop;
break;
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
case "assign":
$stack[] = $prop;
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
if (isset($prop[1][0]) && $prop[1][0] == $this->vPrefix) {
$vars = array_merge($vars, $stack);
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
} else {
$other = array_merge($other, $stack);
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
}
$stack = array();
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
break;
case "import":
$id = self::$nextImportId++;
$prop[] = $id;
$stack[] = $prop;
$imports = array_merge($imports, $stack);
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
$other[] = array("import_mixin", $id);
$stack = array();
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
break;
default:
$stack[] = $prop;
$other = array_merge($other, $stack);
$stack = array();
break;
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
}
}
$other = array_merge($other, $stack);
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
if ($split) {
return array(array_merge($vars, $imports), $other);
} else {
return array_merge($vars, $imports, $other);
}
}
protected function compileMediaQuery($queries) {
$compiledQueries = array();
foreach ($queries as $query) {
$parts = array();
foreach ($query as $q) {
switch ($q[0]) {
case "mediaType":
$parts[] = implode(" ", array_slice($q, 1));
break;
case "mediaExp":
if (isset($q[2])) {
$parts[] = "($q[1]: " .
$this->compileValue($this->reduce($q[2])) . ")";
} else {
$parts[] = "($q[1])";
}
break;
case "variable":
$parts[] = $this->compileValue($this->reduce($q));
break;
}
}
if (count($parts) > 0) {
$compiledQueries[] = implode(" and ", $parts);
}
}
$out = "@media";
if (!empty($parts)) {
$out .= " " .
implode($this->formatter->selectorSeparator, $compiledQueries);
}
return $out;
}
protected function multiplyMedia($env, $childQueries = null) {
if (is_null($env) ||
!empty($env->block->type) && $env->block->type != "media")
{
return $childQueries;
}
// plain old block, skip
if (empty($env->block->type)) {
return $this->multiplyMedia($env->parent, $childQueries);
}
$out = array();
$queries = $env->block->queries;
if (is_null($childQueries)) {
$out = $queries;
} else {
foreach ($queries as $parent) {
foreach ($childQueries as $child) {
$out[] = array_merge($parent, $child);
}
}
}
return $this->multiplyMedia($env->parent, $out);
}
protected function expandParentSelectors(&$tag, $replace) {
$parts = explode("$&$", $tag);
$count = 0;
foreach ($parts as &$part) {
$part = str_replace($this->parentSelector, $replace, $part, $c);
$count += $c;
}
$tag = implode($this->parentSelector, $parts);
return $count;
}
protected function findClosestSelectors() {
$env = $this->env;
$selectors = null;
while ($env !== null) {
if (isset($env->selectors)) {
$selectors = $env->selectors;
break;
}
$env = $env->parent;
}
return $selectors;
}
// multiply $selectors against the nearest selectors in env
protected function multiplySelectors($selectors) {
// find parent selectors
$parentSelectors = $this->findClosestSelectors();
if (is_null($parentSelectors)) {
// kill parent reference in top level selector
foreach ($selectors as &$s) {
$this->expandParentSelectors($s, "");
}
return $selectors;
}
$out = array();
foreach ($parentSelectors as $parent) {
foreach ($selectors as $child) {
$count = $this->expandParentSelectors($child, $parent);
// don't prepend the parent tag if & was used
if ($count > 0) {
$out[] = trim($child);
} else {
$out[] = trim($parent . ' ' . $child);
}
}
}
return $out;
}
// reduces selector expressions
protected function compileSelectors($selectors) {
$out = array();
foreach ($selectors as $s) {
if (is_array($s)) {
list(, $value) = $s;
$out[] = trim($this->compileValue($this->reduce($value)));
} else {
$out[] = $s;
}
}
return $out;
}
protected function eq($left, $right) {
return $left == $right;
}
protected function patternMatch($block, $orderedArgs, $keywordArgs) {
// match the guards if it has them
// any one of the groups must have all its guards pass for a match
if (!empty($block->guards)) {
$groupPassed = false;
foreach ($block->guards as $guardGroup) {
foreach ($guardGroup as $guard) {
$this->pushEnv();
$this->zipSetArgs($block->args, $orderedArgs, $keywordArgs);
$negate = false;
if ($guard[0] == "negate") {
$guard = $guard[1];
$negate = true;
}
$passed = $this->reduce($guard) == self::$TRUE;
if ($negate) $passed = !$passed;
$this->popEnv();
if ($passed) {
$groupPassed = true;
} else {
$groupPassed = false;
break;
}
}
if ($groupPassed) break;
}
if (!$groupPassed) {
return false;
}
}
if (empty($block->args)) {
return $block->isVararg || empty($orderedArgs) && empty($keywordArgs);
}
$remainingArgs = $block->args;
if ($keywordArgs) {
$remainingArgs = array();
foreach ($block->args as $arg) {
if ($arg[0] == "arg" && isset($keywordArgs[$arg[1]])) {
continue;
}
$remainingArgs[] = $arg;
}
}
$i = -1; // no args
// try to match by arity or by argument literal
foreach ($remainingArgs as $i => $arg) {
switch ($arg[0]) {
case "lit":
if (empty($orderedArgs[$i]) || !$this->eq($arg[1], $orderedArgs[$i])) {
return false;
}
break;
case "arg":
// no arg and no default value
if (!isset($orderedArgs[$i]) && !isset($arg[2])) {
return false;
}
break;
case "rest":
$i--; // rest can be empty
break 2;
}
}
if ($block->isVararg) {
return true; // not having enough is handled above
} else {
$numMatched = $i + 1;
// greater than becuase default values always match
return $numMatched >= count($orderedArgs);
}
}
protected function patternMatchAll($blocks, $orderedArgs, $keywordArgs, $skip=array()) {
$matches = null;
foreach ($blocks as $block) {
// skip seen blocks that don't have arguments
if (isset($skip[$block->id]) && !isset($block->args)) {
continue;
}
if ($this->patternMatch($block, $orderedArgs, $keywordArgs)) {
$matches[] = $block;
}
}
return $matches;
}
// attempt to find blocks matched by path and args
protected function findBlocks($searchIn, $path, $orderedArgs, $keywordArgs, $seen=array()) {
if ($searchIn == null) return null;
if (isset($seen[$searchIn->id])) return null;
$seen[$searchIn->id] = true;
$name = $path[0];
if (isset($searchIn->children[$name])) {
$blocks = $searchIn->children[$name];
if (count($path) == 1) {
$matches = $this->patternMatchAll($blocks, $orderedArgs, $keywordArgs, $seen);
if (!empty($matches)) {
// This will return all blocks that match in the closest
// scope that has any matching block, like lessjs
return $matches;
}
} else {
$matches = array();
foreach ($blocks as $subBlock) {
$subMatches = $this->findBlocks($subBlock,
array_slice($path, 1), $orderedArgs, $keywordArgs, $seen);
if (!is_null($subMatches)) {
foreach ($subMatches as $sm) {
$matches[] = $sm;
}
}
}
return count($matches) > 0 ? $matches : null;
}
}
if ($searchIn->parent === $searchIn) return null;
return $this->findBlocks($searchIn->parent, $path, $orderedArgs, $keywordArgs, $seen);
}
// sets all argument names in $args to either the default value
// or the one passed in through $values
protected function zipSetArgs($args, $orderedValues, $keywordValues) {
$assignedValues = array();
$i = 0;
foreach ($args as $a) {
if ($a[0] == "arg") {
if (isset($keywordValues[$a[1]])) {
// has keyword arg
$value = $keywordValues[$a[1]];
} elseif (isset($orderedValues[$i])) {
// has ordered arg
$value = $orderedValues[$i];
$i++;
} elseif (isset($a[2])) {
// has default value
$value = $a[2];
} else {
$this->throwError("Failed to assign arg " . $a[1]);
$value = null; // :(
}
$value = $this->reduce($value);
$this->set($a[1], $value);
$assignedValues[] = $value;
} else {
// a lit
$i++;
}
}
// check for a rest
$last = end($args);
if ($last[0] == "rest") {
$rest = array_slice($orderedValues, count($args) - 1);
$this->set($last[1], $this->reduce(array("list", " ", $rest)));
}
// wow is this the only true use of PHP's + operator for arrays?
$this->env->arguments = $assignedValues + $orderedValues;
}
// compile a prop and update $lines or $blocks appropriately
protected function compileProp($prop, $block, $out) {
// set error position context
$this->sourceLoc = isset($prop[-1]) ? $prop[-1] : -1;
switch ($prop[0]) {
case 'assign':
list(, $name, $value) = $prop;
if ($name[0] == $this->vPrefix) {
$this->set($name, $value);
} else {
$out->lines[] = $this->formatter->property($name,
$this->compileValue($this->reduce($value)));
}
break;
case 'block':
list(, $child) = $prop;
$this->compileBlock($child);
break;
case 'mixin':
list(, $path, $args, $suffix) = $prop;
$orderedArgs = array();
$keywordArgs = array();
foreach ((array)$args as $arg) {
$argval = null;
switch ($arg[0]) {
case "arg":
if (!isset($arg[2])) {
$orderedArgs[] = $this->reduce(array("variable", $arg[1]));
} else {
$keywordArgs[$arg[1]] = $this->reduce($arg[2]);
}
break;
case "lit":
$orderedArgs[] = $this->reduce($arg[1]);
break;
default:
$this->throwError("Unknown arg type: " . $arg[0]);
}
}
$mixins = $this->findBlocks($block, $path, $orderedArgs, $keywordArgs);
if ($mixins === null) {
$this->throwError("{$prop[1][0]} is undefined");
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
}
foreach ($mixins as $mixin) {
if ($mixin === $block && !$orderedArgs) {
continue;
}
$haveScope = false;
if (isset($mixin->parent->scope)) {
$haveScope = true;
$mixinParentEnv = $this->pushEnv();
$mixinParentEnv->storeParent = $mixin->parent->scope;
}
$haveArgs = false;
if (isset($mixin->args)) {
$haveArgs = true;
$this->pushEnv();
$this->zipSetArgs($mixin->args, $orderedArgs, $keywordArgs);
}
$oldParent = $mixin->parent;
if ($mixin != $block) $mixin->parent = $block;
foreach ($this->sortProps($mixin->props) as $subProp) {
if ($suffix !== null &&
$subProp[0] == "assign" &&
is_string($subProp[1]) &&
$subProp[1]{0} != $this->vPrefix)
{
$subProp[2] = array(
'list', ' ',
array($subProp[2], array('keyword', $suffix))
);
}
$this->compileProp($subProp, $mixin, $out);
}
$mixin->parent = $oldParent;
if ($haveArgs) $this->popEnv();
if ($haveScope) $this->popEnv();
}
break;
case 'raw':
$out->lines[] = $prop[1];
break;
case "directive":
list(, $name, $value) = $prop;
$out->lines[] = "@$name " . $this->compileValue($this->reduce($value)).';';
break;
case "comment":
$out->lines[] = $prop[1];
break;
case "import";
list(, $importPath, $importId) = $prop;
$importPath = $this->reduce($importPath);
if (!isset($this->env->imports)) {
$this->env->imports = array();
}
$result = $this->tryImport($importPath, $block, $out);
$this->env->imports[$importId] = $result === false ?
array(false, "@import " . $this->compileValue($importPath).";") :
$result;
break;
case "import_mixin":
list(,$importId) = $prop;
$import = $this->env->imports[$importId];
if ($import[0] === false) {
if (isset($import[1])) {
$out->lines[] = $import[1];
}
} else {
list(, $bottom, $parser, $importDir) = $import;
$this->compileImportedProps($bottom, $block, $out, $parser, $importDir);
}
break;
default:
$this->throwError("unknown op: {$prop[0]}\n");
}
}
/**
* Compiles a primitive value into a CSS property value.
*
* Values in lessphp are typed by being wrapped in arrays, their format is
* typically:
*
* array(type, contents [, additional_contents]*)
*
* The input is expected to be reduced. This function will not work on
* things like expressions and variables.
*/
public function compileValue($value) {
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
switch ($value[0]) {
case 'list':
// [1] - delimiter
// [2] - array of values
return implode($value[1], array_map(array($this, 'compileValue'), $value[2]));
case 'raw_color':
if (!empty($this->formatter->compressColors)) {
return $this->compileValue($this->coerceColor($value));
}
return $value[1];
case 'keyword':
// [1] - the keyword
return $value[1];
case 'number':
list(, $num, $unit) = $value;
// [1] - the number
// [2] - the unit
if ($this->numberPrecision !== null) {
$num = round($num, $this->numberPrecision);
}
return $num . $unit;
case 'string':
// [1] - contents of string (includes quotes)
list(, $delim, $content) = $value;
foreach ($content as &$part) {
if (is_array($part)) {
$part = $this->compileValue($part);
}
}
return $delim . implode($content) . $delim;
case 'color':
// [1] - red component (either number or a %)
// [2] - green component
// [3] - blue component
// [4] - optional alpha component
list(, $r, $g, $b) = $value;
$r = round($r);
$g = round($g);
$b = round($b);
if (count($value) == 5 && $value[4] != 1) { // rgba
return 'rgba('.$r.','.$g.','.$b.','.$value[4].')';
}
$h = sprintf("#%02x%02x%02x", $r, $g, $b);
if (!empty($this->formatter->compressColors)) {
// Converting hex color to short notation (e.g. #003399 to #039)
if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) {
$h = '#' . $h[1] . $h[3] . $h[5];
}
}
return $h;
case 'function':
list(, $name, $args) = $value;
return $name.'('.$this->compileValue($args).')';
default: // assumed to be unit
$this->throwError("unknown value type: $value[0]");
}
}
protected function lib_pow($args) {
list($base, $exp) = $this->assertArgs($args, 2, "pow");
return pow($this->assertNumber($base), $this->assertNumber($exp));
}
protected function lib_pi() {
return pi();
}
protected function lib_mod($args) {
list($a, $b) = $this->assertArgs($args, 2, "mod");
return $this->assertNumber($a) % $this->assertNumber($b);
}
protected function lib_tan($num) {
return tan($this->assertNumber($num));
}
protected function lib_sin($num) {
return sin($this->assertNumber($num));
}
protected function lib_cos($num) {
return cos($this->assertNumber($num));
}
protected function lib_atan($num) {
$num = atan($this->assertNumber($num));
return array("number", $num, "rad");
}
protected function lib_asin($num) {
$num = asin($this->assertNumber($num));
return array("number", $num, "rad");
}
protected function lib_acos($num) {
$num = acos($this->assertNumber($num));
return array("number", $num, "rad");
}
protected function lib_sqrt($num) {
return sqrt($this->assertNumber($num));
}
protected function lib_extract($value) {
list($list, $idx) = $this->assertArgs($value, 2, "extract");
$idx = $this->assertNumber($idx);
// 1 indexed
if ($list[0] == "list" && isset($list[2][$idx - 1])) {
return $list[2][$idx - 1];
}
}
protected function lib_isnumber($value) {
return $this->toBool($value[0] == "number");
}
protected function lib_isstring($value) {
return $this->toBool($value[0] == "string");
}
protected function lib_iscolor($value) {
return $this->toBool($this->coerceColor($value));
}
protected function lib_iskeyword($value) {
return $this->toBool($value[0] == "keyword");
}
protected function lib_ispixel($value) {
return $this->toBool($value[0] == "number" && $value[2] == "px");
}
protected function lib_ispercentage($value) {
return $this->toBool($value[0] == "number" && $value[2] == "%");
}
protected function lib_isem($value) {
return $this->toBool($value[0] == "number" && $value[2] == "em");
}
protected function lib_isrem($value) {
return $this->toBool($value[0] == "number" && $value[2] == "rem");
}
protected function lib_rgbahex($color) {
$color = $this->coerceColor($color);
if (is_null($color))
$this->throwError("color expected for rgbahex");
return sprintf("#%02x%02x%02x%02x",
isset($color[4]) ? $color[4]*255 : 255,
$color[1],$color[2], $color[3]);
}
protected function lib_argb($color){
return $this->lib_rgbahex($color);
}
/**
* Given an url, decide whether to output a regular link or the base64-encoded contents of the file
*
* @param array $value either an argument list (two strings) or a single string
* @return string formatted url(), either as a link or base64-encoded
*/
protected function lib_data_uri($value) {
$mime = ($value[0] === 'list') ? $value[2][0][2] : null;
$url = ($value[0] === 'list') ? $value[2][1][2][0] : $value[2][0];
$fullpath = $this->findImport($url);
if($fullpath && ($fsize = filesize($fullpath)) !== false) {
// IE8 can't handle data uris larger than 32KB
if($fsize/1024 < 32) {
if(is_null($mime)) {
if(class_exists('finfo')) { // php 5.3+
$finfo = new finfo(FILEINFO_MIME);
$mime = explode('; ', $finfo->file($fullpath));
$mime = $mime[0];
} elseif(function_exists('mime_content_type')) { // PHP 5.2
$mime = mime_content_type($fullpath);
}
}
if(!is_null($mime)) // fallback if the mime type is still unknown
$url = sprintf('data:%s;base64,%s', $mime, base64_encode(file_get_contents($fullpath)));
}
}
return 'url("'.$url.'")';
}
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
// utility func to unquote a string
protected function lib_e($arg) {
switch ($arg[0]) {
case "list":
$items = $arg[2];
if (isset($items[0])) {
return $this->lib_e($items[0]);
}
$this->throwError("unrecognised input");
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
case "string":
$arg[1] = "";
return $arg;
case "keyword":
return $arg;
default:
return array("keyword", $this->compileValue($arg));
}
}
protected function lib__sprintf($args) {
if ($args[0] != "list") return $args;
$values = $args[2];
$string = array_shift($values);
$template = $this->compileValue($this->lib_e($string));
$i = 0;
if (preg_match_all('/%[dsa]/', $template, $m)) {
foreach ($m[0] as $match) {
$val = isset($values[$i]) ?
$this->reduce($values[$i]) : array('keyword', '');
// lessjs compat, renders fully expanded color, not raw color
if ($color = $this->coerceColor($val)) {
$val = $color;
}
$i++;
$rep = $this->compileValue($this->lib_e($val));
$template = preg_replace('/'.self::preg_quote($match).'/',
$rep, $template, 1);
}
}
$d = $string[0] == "string" ? $string[1] : '"';
return array("string", $d, array($template));
}
protected function lib_floor($arg) {
$value = $this->assertNumber($arg);
return array("number", floor($value), $arg[2]);
}
protected function lib_ceil($arg) {
$value = $this->assertNumber($arg);
return array("number", ceil($value), $arg[2]);
}
protected function lib_round($arg) {
if($arg[0] != "list") {
$value = $this->assertNumber($arg);
return array("number", round($value), $arg[2]);
} else {
$value = $this->assertNumber($arg[2][0]);
$precision = $this->assertNumber($arg[2][1]);
return array("number", round($value, $precision), $arg[2][0][2]);
}
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
}
protected function lib_unit($arg) {
if ($arg[0] == "list") {
list($number, $newUnit) = $arg[2];
return array("number", $this->assertNumber($number),
$this->compileValue($this->lib_e($newUnit)));
} else {
return array("number", $this->assertNumber($arg), "");
}
}
/**
* Helper function to get arguments for color manipulation functions.
* takes a list that contains a color like thing and a percentage
*/
public function colorArgs($args) {
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
if ($args[0] != 'list' || count($args[2]) < 2) {
return array(array('color', 0, 0, 0), 0);
}
list($color, $delta) = $args[2];
$color = $this->assertColor($color);
$delta = floatval($delta[1]);
return array($color, $delta);
}
protected function lib_darken($args) {
list($color, $delta) = $this->colorArgs($args);
$hsl = $this->toHSL($color);
$hsl[3] = $this->clamp($hsl[3] - $delta, 100);
return $this->toRGB($hsl);
}
protected function lib_lighten($args) {
list($color, $delta) = $this->colorArgs($args);
$hsl = $this->toHSL($color);
$hsl[3] = $this->clamp($hsl[3] + $delta, 100);
return $this->toRGB($hsl);
}
protected function lib_saturate($args) {
list($color, $delta) = $this->colorArgs($args);
$hsl = $this->toHSL($color);
$hsl[2] = $this->clamp($hsl[2] + $delta, 100);
return $this->toRGB($hsl);
}
protected function lib_desaturate($args) {
list($color, $delta) = $this->colorArgs($args);
$hsl = $this->toHSL($color);
$hsl[2] = $this->clamp($hsl[2] - $delta, 100);
return $this->toRGB($hsl);
}
protected function lib_spin($args) {
list($color, $delta) = $this->colorArgs($args);
$hsl = $this->toHSL($color);
$hsl[1] = $hsl[1] + $delta % 360;
if ($hsl[1] < 0) $hsl[1] += 360;
return $this->toRGB($hsl);
}
protected function lib_fadeout($args) {
list($color, $delta) = $this->colorArgs($args);
$color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) - $delta/100);
return $color;
}
protected function lib_fadein($args) {
list($color, $delta) = $this->colorArgs($args);
$color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) + $delta/100);
return $color;
}
protected function lib_hue($color) {
$hsl = $this->toHSL($this->assertColor($color));
return round($hsl[1]);
}
protected function lib_saturation($color) {
$hsl = $this->toHSL($this->assertColor($color));
return round($hsl[2]);
}
protected function lib_lightness($color) {
$hsl = $this->toHSL($this->assertColor($color));
return round($hsl[3]);
}
// get the alpha of a color
// defaults to 1 for non-colors or colors without an alpha
protected function lib_alpha($value) {
if (!is_null($color = $this->coerceColor($value))) {
return isset($color[4]) ? $color[4] : 1;
}
}
// set the alpha of the color
protected function lib_fade($args) {
list($color, $alpha) = $this->colorArgs($args);
$color[4] = $this->clamp($alpha / 100.0);
return $color;
}
protected function lib_percentage($arg) {
$num = $this->assertNumber($arg);
return array("number", $num*100, "%");
}
// mixes two colors by weight
// mix(@color1, @color2, [@weight: 50%]);
// http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_method
protected function lib_mix($args) {
if ($args[0] != "list" || count($args[2]) < 2)
$this->throwError("mix expects (color1, color2, weight)");
list($first, $second) = $args[2];
$first = $this->assertColor($first);
$second = $this->assertColor($second);
$first_a = $this->lib_alpha($first);
$second_a = $this->lib_alpha($second);
if (isset($args[2][2])) {
$weight = $args[2][2][1] / 100.0;
} else {
$weight = 0.5;
}
$w = $weight * 2 - 1;
$a = $first_a - $second_a;
$w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0;
$w2 = 1.0 - $w1;
$new = array('color',
$w1 * $first[1] + $w2 * $second[1],
$w1 * $first[2] + $w2 * $second[2],
$w1 * $first[3] + $w2 * $second[3],
);
if ($first_a != 1.0 || $second_a != 1.0) {
$new[] = $first_a * $weight + $second_a * ($weight - 1);
}
return $this->fixColor($new);
}
protected function lib_contrast($args) {
$darkColor = array('color', 0, 0, 0);
$lightColor = array('color', 255, 255, 255);
$threshold = 0.43;
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
if ( $args[0] == 'list' ) {
$inputColor = ( isset($args[2][0]) ) ? $this->assertColor($args[2][0]) : $lightColor;
$darkColor = ( isset($args[2][1]) ) ? $this->assertColor($args[2][1]) : $darkColor;
$lightColor = ( isset($args[2][2]) ) ? $this->assertColor($args[2][2]) : $lightColor;
$threshold = ( isset($args[2][3]) ) ? $this->assertNumber($args[2][3]) : $threshold;
}
else {
$inputColor = $this->assertColor($args);
}
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
$inputColor = $this->coerceColor($inputColor);
$darkColor = $this->coerceColor($darkColor);
$lightColor = $this->coerceColor($lightColor);
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
//Figure out which is actually light and dark!
if ( $this->lib_luma($darkColor) > $this->lib_luma($lightColor) ) {
$t = $lightColor;
$lightColor = $darkColor;
$darkColor = $t;
}
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
$inputColor_alpha = $this->lib_alpha($inputColor);
if ( ( $this->lib_luma($inputColor) * $inputColor_alpha) < $threshold) {
return $lightColor;
}
return $darkColor;
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
}
protected function lib_luma($color) {
$color = $this->coerceColor($color);
return (0.2126 * $color[0] / 255) + (0.7152 * $color[1] / 255) + (0.0722 * $color[2] / 255);
}
public function assertColor($value, $error = "expected color value") {
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
$color = $this->coerceColor($value);
if (is_null($color)) $this->throwError($error);
return $color;
}
public function assertNumber($value, $error = "expecting number") {
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
if ($value[0] == "number") return $value[1];
$this->throwError($error);
}
public function assertArgs($value, $expectedArgs, $name="") {
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
if ($expectedArgs == 1) {
return $value;
} else {
if ($value[0] !== "list" || $value[1] != ",") $this->throwError("expecting list");
$values = $value[2];
$numValues = count($values);
if ($expectedArgs != $numValues) {
if ($name) {
$name = $name . ": ";
}
$this->throwError("${name}expecting $expectedArgs arguments, got $numValues");
}
return $values;
}
}
protected function toHSL($color) {
if ($color[0] == 'hsl') return $color;
$r = $color[1] / 255;
$g = $color[2] / 255;
$b = $color[3] / 255;
$min = min($r, $g, $b);
$max = max($r, $g, $b);
$L = ($min + $max) / 2;
if ($min == $max) {
$S = $H = 0;
} else {
if ($L < 0.5)
$S = ($max - $min)/($max + $min);
else
$S = ($max - $min)/(2.0 - $max - $min);
if ($r == $max) $H = ($g - $b)/($max - $min);
elseif ($g == $max) $H = 2.0 + ($b - $r)/($max - $min);
elseif ($b == $max) $H = 4.0 + ($r - $g)/($max - $min);
}
$out = array('hsl',
($H < 0 ? $H + 6 : $H)*60,
$S*100,
$L*100,
);
if (count($color) > 4) $out[] = $color[4]; // copy alpha
return $out;
}
protected function toRGB_helper($comp, $temp1, $temp2) {
if ($comp < 0) $comp += 1.0;
elseif ($comp > 1) $comp -= 1.0;
if (6 * $comp < 1) return $temp1 + ($temp2 - $temp1) * 6 * $comp;
if (2 * $comp < 1) return $temp2;
if (3 * $comp < 2) return $temp1 + ($temp2 - $temp1)*((2/3) - $comp) * 6;
return $temp1;
}
/**
* Converts a hsl array into a color value in rgb.
* Expects H to be in range of 0 to 360, S and L in 0 to 100
*/
protected function toRGB($color) {
if ($color[0] == 'color') return $color;
$H = $color[1] / 360;
$S = $color[2] / 100;
$L = $color[3] / 100;
if ($S == 0) {
$r = $g = $b = $L;
} else {
$temp2 = $L < 0.5 ?
$L*(1.0 + $S) :
$L + $S - $L * $S;
$temp1 = 2.0 * $L - $temp2;
$r = $this->toRGB_helper($H + 1/3, $temp1, $temp2);
$g = $this->toRGB_helper($H, $temp1, $temp2);
$b = $this->toRGB_helper($H - 1/3, $temp1, $temp2);
}
// $out = array('color', round($r*255), round($g*255), round($b*255));
$out = array('color', $r*255, $g*255, $b*255);
if (count($color) > 4) $out[] = $color[4]; // copy alpha
return $out;
}
protected function clamp($v, $max = 1, $min = 0) {
return min($max, max($min, $v));
}
/**
* Convert the rgb, rgba, hsl color literals of function type
* as returned by the parser into values of color type.
*/
protected function funcToColor($func) {
$fname = $func[1];
if ($func[2][0] != 'list') return false; // need a list of arguments
$rawComponents = $func[2][2];
if ($fname == 'hsl' || $fname == 'hsla') {
$hsl = array('hsl');
$i = 0;
foreach ($rawComponents as $c) {
$val = $this->reduce($c);
$val = isset($val[1]) ? floatval($val[1]) : 0;
if ($i == 0) $clamp = 360;
elseif ($i < 3) $clamp = 100;
else $clamp = 1;
$hsl[] = $this->clamp($val, $clamp);
$i++;
}
while (count($hsl) < 4) $hsl[] = 0;
return $this->toRGB($hsl);
} elseif ($fname == 'rgb' || $fname == 'rgba') {
$components = array();
$i = 1;
foreach ($rawComponents as $c) {
$c = $this->reduce($c);
if ($i < 4) {
if ($c[0] == "number" && $c[2] == "%") {
$components[] = 255 * ($c[1] / 100);
} else {
$components[] = floatval($c[1]);
}
} elseif ($i == 4) {
if ($c[0] == "number" && $c[2] == "%") {
$components[] = 1.0 * ($c[1] / 100);
} else {
$components[] = floatval($c[1]);
}
} else break;
$i++;
}
while (count($components) < 3) $components[] = 0;
array_unshift($components, 'color');
return $this->fixColor($components);
}
return false;
}
protected function reduce($value, $forExpression = false) {
switch ($value[0]) {
case "interpolate":
$reduced = $this->reduce($value[1]);
$var = $this->compileValue($reduced);
$res = $this->reduce(array("variable", $this->vPrefix . $var));
if ($res[0] == "raw_color") {
$res = $this->coerceColor($res);
}
if (empty($value[2])) $res = $this->lib_e($res);
return $res;
case "variable":
$key = $value[1];
if (is_array($key)) {
$key = $this->reduce($key);
$key = $this->vPrefix . $this->compileValue($this->lib_e($key));
}
$seen =& $this->env->seenNames;
if (!empty($seen[$key])) {
$this->throwError("infinite loop detected: $key");
}
$seen[$key] = true;
$out = $this->reduce($this->get($key));
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
$seen[$key] = false;
return $out;
case "list":
foreach ($value[2] as &$item) {
$item = $this->reduce($item, $forExpression);
}
return $value;
case "expression":
return $this->evaluate($value);
case "string":
foreach ($value[2] as &$part) {
if (is_array($part)) {
$strip = $part[0] == "variable";
$part = $this->reduce($part);
if ($strip) $part = $this->lib_e($part);
}
}
return $value;
case "escape":
list(,$inner) = $value;
return $this->lib_e($this->reduce($inner));
case "function":
$color = $this->funcToColor($value);
if ($color) return $color;
list(, $name, $args) = $value;
if ($name == "%") $name = "_sprintf";
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
$f = isset($this->libFunctions[$name]) ?
$this->libFunctions[$name] : array($this, 'lib_'.str_replace('-', '_', $name));
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
if (is_callable($f)) {
if ($args[0] == 'list')
$args = self::compressList($args[2], $args[1]);
$ret = call_user_func($f, $this->reduce($args, true), $this);
if (is_null($ret)) {
return array("string", "", array(
$name, "(", $args, ")"
));
}
// convert to a typed value if the result is a php primitive
if (is_numeric($ret)) $ret = array('number', $ret, "");
elseif (!is_array($ret)) $ret = array('keyword', $ret);
return $ret;
}
// plain function, reduce args
$value[2] = $this->reduce($value[2]);
return $value;
case "unary":
list(, $op, $exp) = $value;
$exp = $this->reduce($exp);
if ($exp[0] == "number") {
switch ($op) {
case "+":
return $exp;
case "-":
$exp[1] *= -1;
return $exp;
}
}
return array("string", "", array($op, $exp));
}
if ($forExpression) {
switch ($value[0]) {
case "keyword":
if ($color = $this->coerceColor($value)) {
return $color;
}
break;
case "raw_color":
return $this->coerceColor($value);
}
}
return $value;
}
// coerce a value for use in color operation
protected function coerceColor($value) {
switch($value[0]) {
case 'color': return $value;
case 'raw_color':
$c = array("color", 0, 0, 0);
$colorStr = substr($value[1], 1);
$num = hexdec($colorStr);
$width = strlen($colorStr) == 3 ? 16 : 256;
for ($i = 3; $i > 0; $i--) { // 3 2 1
$t = $num % $width;
$num /= $width;
$c[$i] = $t * (256/$width) + $t * floor(16/$width);
}
return $c;
case 'keyword':
$name = $value[1];
if (isset(self::$cssColors[$name])) {
$rgba = explode(',', self::$cssColors[$name]);
if(isset($rgba[3]))
return array('color', $rgba[0], $rgba[1], $rgba[2], $rgba[3]);
return array('color', $rgba[0], $rgba[1], $rgba[2]);
}
return null;
}
}
// make something string like into a string
protected function coerceString($value) {
switch ($value[0]) {
case "string":
return $value;
case "keyword":
return array("string", "", array($value[1]));
}
return null;
}
// turn list of length 1 into value type
protected function flattenList($value) {
if ($value[0] == "list" && count($value[2]) == 1) {
return $this->flattenList($value[2][0]);
}
return $value;
}
public function toBool($a) {
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
if ($a) return self::$TRUE;
else return self::$FALSE;
}
// evaluate an expression
protected function evaluate($exp) {
list(, $op, $left, $right, $whiteBefore, $whiteAfter) = $exp;
$left = $this->reduce($left, true);
$right = $this->reduce($right, true);
if ($leftColor = $this->coerceColor($left)) {
$left = $leftColor;
}
if ($rightColor = $this->coerceColor($right)) {
$right = $rightColor;
}
$ltype = $left[0];
$rtype = $right[0];
// operators that work on all types
if ($op == "and") {
return $this->toBool($left == self::$TRUE && $right == self::$TRUE);
}
if ($op == "=") {
return $this->toBool($this->eq($left, $right) );
}
if ($op == "+" && !is_null($str = $this->stringConcatenate($left, $right))) {
return $str;
}
// type based operators
$fname = "op_${ltype}_${rtype}";
if (is_callable(array($this, $fname))) {
$out = $this->$fname($op, $left, $right);
if (!is_null($out)) return $out;
}
// make the expression look it did before being parsed
$paddedOp = $op;
if ($whiteBefore) $paddedOp = " " . $paddedOp;
if ($whiteAfter) $paddedOp .= " ";
return array("string", "", array($left, $paddedOp, $right));
}
protected function stringConcatenate($left, $right) {
if ($strLeft = $this->coerceString($left)) {
if ($right[0] == "string") {
$right[1] = "";
}
$strLeft[2][] = $right;
return $strLeft;
}
if ($strRight = $this->coerceString($right)) {
array_unshift($strRight[2], $left);
return $strRight;
}
}
// make sure a color's components don't go out of bounds
protected function fixColor($c) {
foreach (range(1, 3) as $i) {
if ($c[$i] < 0) $c[$i] = 0;
if ($c[$i] > 255) $c[$i] = 255;
}
return $c;
}
protected function op_number_color($op, $lft, $rgt) {
if ($op == '+' || $op == '*') {
return $this->op_color_number($op, $rgt, $lft);
}
}
protected function op_color_number($op, $lft, $rgt) {
if ($rgt[0] == '%') $rgt[1] /= 100;
return $this->op_color_color($op, $lft,
array_fill(1, count($lft) - 1, $rgt[1]));
}
protected function op_color_color($op, $left, $right) {
$out = array('color');
$max = count($left) > count($right) ? count($left) : count($right);
foreach (range(1, $max - 1) as $i) {
$lval = isset($left[$i]) ? $left[$i] : 0;
$rval = isset($right[$i]) ? $right[$i] : 0;
switch ($op) {
case '+':
$out[] = $lval + $rval;
break;
case '-':
$out[] = $lval - $rval;
break;
case '*':
$out[] = $lval * $rval;
break;
case '%':
$out[] = $lval % $rval;
break;
case '/':
if ($rval == 0) $this->throwError("evaluate error: can't divide by zero");
$out[] = $lval / $rval;
break;
default:
$this->throwError('evaluate error: color op number failed on op '.$op);
}
}
return $this->fixColor($out);
}
function lib_red($color){
$color = $this->coerceColor($color);
if (is_null($color)) {
$this->throwError('color expected for red()');
}
return $color[1];
}
function lib_green($color){
$color = $this->coerceColor($color);
if (is_null($color)) {
$this->throwError('color expected for green()');
}
return $color[2];
}
function lib_blue($color){
$color = $this->coerceColor($color);
if (is_null($color)) {
$this->throwError('color expected for blue()');
}
return $color[3];
}
// operator on two numbers
protected function op_number_number($op, $left, $right) {
$unit = empty($left[2]) ? $right[2] : $left[2];
$value = 0;
switch ($op) {
case '+':
$value = $left[1] + $right[1];
break;
case '*':
$value = $left[1] * $right[1];
break;
case '-':
$value = $left[1] - $right[1];
break;
case '%':
$value = $left[1] % $right[1];
break;
case '/':
if ($right[1] == 0) $this->throwError('parse error: divide by zero');
$value = $left[1] / $right[1];
break;
case '<':
return $this->toBool($left[1] < $right[1]);
case '>':
return $this->toBool($left[1] > $right[1]);
case '>=':
return $this->toBool($left[1] >= $right[1]);
case '=<':
return $this->toBool($left[1] <= $right[1]);
default:
$this->throwError('parse error: unknown number operator: '.$op);
}
return array("number", $value, $unit);
}
/* environment functions */
protected function makeOutputBlock($type, $selectors = null) {
$b = new stdclass;
$b->lines = array();
$b->children = array();
$b->selectors = $selectors;
$b->type = $type;
$b->parent = $this->scope;
return $b;
}
// the state of execution
protected function pushEnv($block = null) {
$e = new stdclass;
$e->parent = $this->env;
$e->store = array();
$e->block = $block;
$this->env = $e;
return $e;
}
// pop something off the stack
protected function popEnv() {
$old = $this->env;
$this->env = $this->env->parent;
return $old;
}
// set something in the current env
protected function set($name, $value) {
$this->env->store[$name] = $value;
}
// get the highest occurrence entry for a name
protected function get($name) {
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
$current = $this->env;
$isArguments = $name == $this->vPrefix . 'arguments';
while ($current) {
if ($isArguments && isset($current->arguments)) {
return array('list', ' ', $current->arguments);
}
if (isset($current->store[$name]))
return $current->store[$name];
else {
$current = isset($current->storeParent) ?
$current->storeParent : $current->parent;
}
}
$this->throwError("variable $name is undefined");
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
}
// inject array of unparsed strings into environment as variables
protected function injectVariables($args) {
$this->pushEnv();
$parser = new lessc_parser($this, __METHOD__);
foreach ($args as $name => $strValue) {
if ($name{0} != '@') $name = '@'.$name;
$parser->count = 0;
$parser->buffer = (string)$strValue;
if (!$parser->propertyValue($value)) {
throw new Exception("failed to parse passed in variable $name: $strValue");
}
$this->set($name, $value);
}
}
/**
* Initialize any static state, can initialize parser for a file
* $opts isn't used yet
*/
public function __construct($fname = null) {
if ($fname !== null) {
// used for deprecated parse method
$this->_parseFile = $fname;
}
}
public function compile($string, $name = null) {
$locale = setlocale(LC_NUMERIC, 0);
setlocale(LC_NUMERIC, "C");
$this->parser = $this->makeParser($name);
$root = $this->parser->parse($string);
$this->env = null;
$this->scope = null;
$this->formatter = $this->newFormatter();
if (!empty($this->registeredVars)) {
$this->injectVariables($this->registeredVars);
}
$this->sourceParser = $this->parser; // used for error messages
$this->compileBlock($root);
ob_start();
$this->formatter->block($this->scope);
$out = ob_get_clean();
setlocale(LC_NUMERIC, $locale);
return $out;
}
public function compileFile($fname, $outFname = null) {
if (!is_readable($fname)) {
throw new Exception('load error: failed to find '.$fname);
}
$pi = pathinfo($fname);
$oldImport = $this->importDir;
$this->importDir = (array)$this->importDir;
$this->importDir[] = $pi['dirname'].'/';
$this->addParsedFile($fname);
$out = $this->compile(file_get_contents($fname), $fname);
$this->importDir = $oldImport;
if ($outFname !== null) {
return file_put_contents($outFname, $out);
}
return $out;
}
// compile only if changed input has changed or output doesn't exist
public function checkedCompile($in, $out) {
if (!is_file($out) || filemtime($in) > filemtime($out)) {
$this->compileFile($in, $out);
return true;
}
return false;
}
/**
* Execute lessphp on a .less file or a lessphp cache structure
*
* The lessphp cache structure contains information about a specific
* less file having been parsed. It can be used as a hint for future
* calls to determine whether or not a rebuild is required.
*
* The cache structure contains two important keys that may be used
* externally:
*
* compiled: The final compiled CSS
* updated: The time (in seconds) the CSS was last compiled
*
* The cache structure is a plain-ol' PHP associative array and can
* be serialized and unserialized without a hitch.
*
* @param mixed $in Input
* @param bool $force Force rebuild?
* @return array lessphp cache structure
*/
public function cachedCompile($in, $force = false) {
// assume no root
$root = null;
if (is_string($in)) {
$root = $in;
} elseif (is_array($in) and isset($in['root'])) {
if ($force or ! isset($in['files'])) {
// If we are forcing a recompile or if for some reason the
// structure does not contain any file information we should
// specify the root to trigger a rebuild.
$root = $in['root'];
} elseif (isset($in['files']) and is_array($in['files'])) {
foreach ($in['files'] as $fname => $ftime ) {
if (!file_exists($fname) or filemtime($fname) > $ftime) {
// One of the files we knew about previously has changed
// so we should look at our incoming root again.
$root = $in['root'];
break;
}
}
}
} else {
// TODO: Throw an exception? We got neither a string nor something
// that looks like a compatible lessphp cache structure.
return null;
}
if ($root !== null) {
// If we have a root value which means we should rebuild.
$out = array();
$out['root'] = $root;
$out['compiled'] = $this->compileFile($root);
$out['files'] = $this->allParsedFiles();
$out['updated'] = time();
return $out;
} else {
// No changes, pass back the structure
// we were given initially.
return $in;
}
}
// parse and compile buffer
// This is deprecated
public function parse($str = null, $initialVariables = null) {
if (is_array($str)) {
$initialVariables = $str;
$str = null;
}
$oldVars = $this->registeredVars;
if ($initialVariables !== null) {
$this->setVariables($initialVariables);
}
if ($str == null) {
if (empty($this->_parseFile)) {
throw new exception("nothing to parse");
}
$out = $this->compileFile($this->_parseFile);
} else {
$out = $this->compile($str);
}
$this->registeredVars = $oldVars;
return $out;
}
protected function makeParser($name) {
$parser = new lessc_parser($this, $name);
$parser->writeComments = $this->preserveComments;
return $parser;
}
public function setFormatter($name) {
$this->formatterName = $name;
}
protected function newFormatter() {
$className = "lessc_formatter_lessjs";
if (!empty($this->formatterName)) {
if (!is_string($this->formatterName))
return $this->formatterName;
$className = "lessc_formatter_$this->formatterName";
}
return new $className;
}
public function setPreserveComments($preserve) {
$this->preserveComments = $preserve;
}
public function registerFunction($name, $func) {
$this->libFunctions[$name] = $func;
}
public function unregisterFunction($name) {
unset($this->libFunctions[$name]);
}
public function setVariables($variables) {
$this->registeredVars = array_merge($this->registeredVars, $variables);
}
public function unsetVariable($name) {
unset($this->registeredVars[$name]);
}
public function setImportDir($dirs) {
$this->importDir = (array)$dirs;
}
public function addImportDir($dir) {
$this->importDir = (array)$this->importDir;
$this->importDir[] = $dir;
}
public function allParsedFiles() {
return $this->allParsedFiles;
}
public function addParsedFile($file) {
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
$this->allParsedFiles[realpath($file)] = filemtime($file);
}
/**
* Uses the current value of $this->count to show line and line number
*/
public function throwError($msg = null) {
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
if ($this->sourceLoc >= 0) {
$this->sourceParser->throwError($msg, $this->sourceLoc);
}
throw new exception($msg);
}
// compile file $in to file $out if $in is newer than $out
// returns true when it compiles, false otherwise
public static function ccompile($in, $out, $less = null) {
if ($less === null) {
$less = new self;
}
return $less->checkedCompile($in, $out);
}
public static function cexecute($in, $force = false, $less = null) {
if ($less === null) {
$less = new self;
}
return $less->cachedCompile($in, $force);
}
static protected $cssColors = array(
'aliceblue' => '240,248,255',
'antiquewhite' => '250,235,215',
'aqua' => '0,255,255',
'aquamarine' => '127,255,212',
'azure' => '240,255,255',
'beige' => '245,245,220',
'bisque' => '255,228,196',
'black' => '0,0,0',
'blanchedalmond' => '255,235,205',
'blue' => '0,0,255',
'blueviolet' => '138,43,226',
'brown' => '165,42,42',
'burlywood' => '222,184,135',
'cadetblue' => '95,158,160',
'chartreuse' => '127,255,0',
'chocolate' => '210,105,30',
'coral' => '255,127,80',
'cornflowerblue' => '100,149,237',
'cornsilk' => '255,248,220',
'crimson' => '220,20,60',
'cyan' => '0,255,255',
'darkblue' => '0,0,139',
'darkcyan' => '0,139,139',
'darkgoldenrod' => '184,134,11',
'darkgray' => '169,169,169',
'darkgreen' => '0,100,0',
'darkgrey' => '169,169,169',
'darkkhaki' => '189,183,107',
'darkmagenta' => '139,0,139',
'darkolivegreen' => '85,107,47',
'darkorange' => '255,140,0',
'darkorchid' => '153,50,204',
'darkred' => '139,0,0',
'darksalmon' => '233,150,122',
'darkseagreen' => '143,188,143',
'darkslateblue' => '72,61,139',
'darkslategray' => '47,79,79',
'darkslategrey' => '47,79,79',
'darkturquoise' => '0,206,209',
'darkviolet' => '148,0,211',
'deeppink' => '255,20,147',
'deepskyblue' => '0,191,255',
'dimgray' => '105,105,105',
'dimgrey' => '105,105,105',
'dodgerblue' => '30,144,255',
'firebrick' => '178,34,34',
'floralwhite' => '255,250,240',
'forestgreen' => '34,139,34',
'fuchsia' => '255,0,255',
'gainsboro' => '220,220,220',
'ghostwhite' => '248,248,255',
'gold' => '255,215,0',
'goldenrod' => '218,165,32',
'gray' => '128,128,128',
'green' => '0,128,0',
'greenyellow' => '173,255,47',
'grey' => '128,128,128',
'honeydew' => '240,255,240',
'hotpink' => '255,105,180',
'indianred' => '205,92,92',
'indigo' => '75,0,130',
'ivory' => '255,255,240',
'khaki' => '240,230,140',
'lavender' => '230,230,250',
'lavenderblush' => '255,240,245',
'lawngreen' => '124,252,0',
'lemonchiffon' => '255,250,205',
'lightblue' => '173,216,230',
'lightcoral' => '240,128,128',
'lightcyan' => '224,255,255',
'lightgoldenrodyellow' => '250,250,210',
'lightgray' => '211,211,211',
'lightgreen' => '144,238,144',
'lightgrey' => '211,211,211',
'lightpink' => '255,182,193',
'lightsalmon' => '255,160,122',
'lightseagreen' => '32,178,170',
'lightskyblue' => '135,206,250',
'lightslategray' => '119,136,153',
'lightslategrey' => '119,136,153',
'lightsteelblue' => '176,196,222',
'lightyellow' => '255,255,224',
'lime' => '0,255,0',
'limegreen' => '50,205,50',
'linen' => '250,240,230',
'magenta' => '255,0,255',
'maroon' => '128,0,0',
'mediumaquamarine' => '102,205,170',
'mediumblue' => '0,0,205',
'mediumorchid' => '186,85,211',
'mediumpurple' => '147,112,219',
'mediumseagreen' => '60,179,113',
'mediumslateblue' => '123,104,238',
'mediumspringgreen' => '0,250,154',
'mediumturquoise' => '72,209,204',
'mediumvioletred' => '199,21,133',
'midnightblue' => '25,25,112',
'mintcream' => '245,255,250',
'mistyrose' => '255,228,225',
'moccasin' => '255,228,181',
'navajowhite' => '255,222,173',
'navy' => '0,0,128',
'oldlace' => '253,245,230',
'olive' => '128,128,0',
'olivedrab' => '107,142,35',
'orange' => '255,165,0',
'orangered' => '255,69,0',
'orchid' => '218,112,214',
'palegoldenrod' => '238,232,170',
'palegreen' => '152,251,152',
'paleturquoise' => '175,238,238',
'palevioletred' => '219,112,147',
'papayawhip' => '255,239,213',
'peachpuff' => '255,218,185',
'peru' => '205,133,63',
'pink' => '255,192,203',
'plum' => '221,160,221',
'powderblue' => '176,224,230',
'purple' => '128,0,128',
'red' => '255,0,0',
'rosybrown' => '188,143,143',
'royalblue' => '65,105,225',
'saddlebrown' => '139,69,19',
'salmon' => '250,128,114',
'sandybrown' => '244,164,96',
'seagreen' => '46,139,87',
'seashell' => '255,245,238',
'sienna' => '160,82,45',
'silver' => '192,192,192',
'skyblue' => '135,206,235',
'slateblue' => '106,90,205',
'slategray' => '112,128,144',
'slategrey' => '112,128,144',
'snow' => '255,250,250',
'springgreen' => '0,255,127',
'steelblue' => '70,130,180',
'tan' => '210,180,140',
'teal' => '0,128,128',
'thistle' => '216,191,216',
'tomato' => '255,99,71',
'transparent' => '0,0,0,0',
'turquoise' => '64,224,208',
'violet' => '238,130,238',
'wheat' => '245,222,179',
'white' => '255,255,255',
'whitesmoke' => '245,245,245',
'yellow' => '255,255,0',
'yellowgreen' => '154,205,50'
);
}
// responsible for taking a string of LESS code and converting it into a
// syntax tree
class lessc_parser {
static protected $nextBlockId = 0; // used to uniquely identify blocks
static protected $precedence = array(
'=<' => 0,
'>=' => 0,
'=' => 0,
'<' => 0,
'>' => 0,
'+' => 1,
'-' => 1,
'*' => 2,
'/' => 2,
'%' => 2,
);
static protected $whitePattern;
static protected $commentMulti;
static protected $commentSingle = "//";
static protected $commentMultiLeft = "/*";
static protected $commentMultiRight = "*/";
// regex string to match any of the operators
static protected $operatorString;
// these properties will supress division unless it's inside parenthases
static protected $supressDivisionProps =
array('/border-radius$/i', '/^font$/i');
protected $blockDirectives = array("font-face", "keyframes", "page", "-moz-document", "viewport", "-moz-viewport", "-o-viewport", "-ms-viewport");
protected $lineDirectives = array("charset");
/**
* if we are in parens we can be more liberal with whitespace around
* operators because it must evaluate to a single value and thus is less
* ambiguous.
*
* Consider:
* property1: 10 -5; // is two numbers, 10 and -5
* property2: (10 -5); // should evaluate to 5
*/
protected $inParens = false;
// caches preg escaped literals
static protected $literalCache = array();
public function __construct($lessc, $sourceName = null) {
$this->eatWhiteDefault = true;
// reference to less needed for vPrefix, mPrefix, and parentSelector
$this->lessc = $lessc;
$this->sourceName = $sourceName; // name used for error messages
$this->writeComments = false;
if (!self::$operatorString) {
self::$operatorString =
'('.implode('|', array_map(array('lessc', 'preg_quote'),
array_keys(self::$precedence))).')';
$commentSingle = lessc::preg_quote(self::$commentSingle);
$commentMultiLeft = lessc::preg_quote(self::$commentMultiLeft);
$commentMultiRight = lessc::preg_quote(self::$commentMultiRight);
self::$commentMulti = $commentMultiLeft.'.*?'.$commentMultiRight;
self::$whitePattern = '/'.$commentSingle.'[^\n]*\s*|('.self::$commentMulti.')\s*|\s+/Ais';
}
}
public function parse($buffer) {
$this->count = 0;
$this->line = 1;
$this->env = null; // block stack
$this->buffer = $this->writeComments ? $buffer : $this->removeComments($buffer);
$this->pushSpecialBlock("root");
$this->eatWhiteDefault = true;
$this->seenComments = array();
// trim whitespace on head
// if (preg_match('/^\s+/', $this->buffer, $m)) {
// $this->line += substr_count($m[0], "\n");
// $this->buffer = ltrim($this->buffer);
// }
$this->whitespace();
// parse the entire file
while (false !== $this->parseChunk());
if ($this->count != strlen($this->buffer))
$this->throwError();
// TODO report where the block was opened
if ( !property_exists($this->env, 'parent') || !is_null($this->env->parent) )
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
throw new exception('parse error: unclosed block');
return $this->env;
}
/**
* Parse a single chunk off the head of the buffer and append it to the
* current parse environment.
* Returns false when the buffer is empty, or when there is an error.
*
* This function is called repeatedly until the entire document is
* parsed.
*
* This parser is most similar to a recursive descent parser. Single
* functions represent discrete grammatical rules for the language, and
* they are able to capture the text that represents those rules.
*
* Consider the function lessc::keyword(). (all parse functions are
* structured the same)
*
* The function takes a single reference argument. When calling the
* function it will attempt to match a keyword on the head of the buffer.
* If it is successful, it will place the keyword in the referenced
* argument, advance the position in the buffer, and return true. If it
* fails then it won't advance the buffer and it will return false.
*
* All of these parse functions are powered by lessc::match(), which behaves
* the same way, but takes a literal regular expression. Sometimes it is
* more convenient to use match instead of creating a new function.
*
* Because of the format of the functions, to parse an entire string of
* grammatical rules, you can chain them together using &&.
*
* But, if some of the rules in the chain succeed before one fails, then
* the buffer position will be left at an invalid state. In order to
* avoid this, lessc::seek() is used to remember and set buffer positions.
*
* Before parsing a chain, use $s = $this->seek() to remember the current
* position into $s. Then if a chain fails, use $this->seek($s) to
* go back where we started.
*/
protected function parseChunk() {
if (empty($this->buffer)) return false;
$s = $this->seek();
if ($this->whitespace()) {
return true;
}
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
// setting a property
if ($this->keyword($key) && $this->assign() &&
$this->propertyValue($value, $key) && $this->end())
{
$this->append(array('assign', $key, $value), $s);
return true;
} else {
$this->seek($s);
}
// look for special css blocks
if ($this->literal('@', false)) {
$this->count--;
// media
if ($this->literal('@media')) {
if (($this->mediaQueryList($mediaQueries) || true)
&& $this->literal('{'))
{
$media = $this->pushSpecialBlock("media");
$media->queries = is_null($mediaQueries) ? array() : $mediaQueries;
return true;
} else {
$this->seek($s);
return false;
}
}
if ($this->literal("@", false) && $this->keyword($dirName)) {
if ($this->isDirective($dirName, $this->blockDirectives)) {
if (($this->openString("{", $dirValue, null, array(";")) || true) &&
$this->literal("{"))
{
$dir = $this->pushSpecialBlock("directive");
$dir->name = $dirName;
if (isset($dirValue)) $dir->value = $dirValue;
return true;
}
} elseif ($this->isDirective($dirName, $this->lineDirectives)) {
if ($this->propertyValue($dirValue) && $this->end()) {
$this->append(array("directive", $dirName, $dirValue));
return true;
}
}
}
$this->seek($s);
}
// setting a variable
if ($this->variable($var) && $this->assign() &&
$this->propertyValue($value) && $this->end())
{
$this->append(array('assign', $var, $value), $s);
return true;
} else {
$this->seek($s);
}
if ($this->import($importValue)) {
$this->append($importValue, $s);
return true;
}
// opening parametric mixin
if ($this->tag($tag, true) && $this->argumentDef($args, $isVararg) &&
($this->guards($guards) || true) &&
$this->literal('{'))
{
$block = $this->pushBlock($this->fixTags(array($tag)));
$block->args = $args;
$block->isVararg = $isVararg;
if (!empty($guards)) $block->guards = $guards;
return true;
} else {
$this->seek($s);
}
// opening a simple block
if ($this->tags($tags) && $this->literal('{', false)) {
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
$tags = $this->fixTags($tags);
$this->pushBlock($tags);
return true;
} else {
$this->seek($s);
}
// closing a block
if ($this->literal('}', false)) {
try {
$block = $this->pop();
} catch (exception $e) {
$this->seek($s);
$this->throwError($e->getMessage());
}
$hidden = false;
if (is_null($block->type)) {
$hidden = true;
if (!isset($block->args)) {
foreach ($block->tags as $tag) {
if (!is_string($tag) || $tag{0} != $this->lessc->mPrefix) {
$hidden = false;
break;
}
}
}
foreach ($block->tags as $tag) {
if (is_string($tag)) {
$this->env->children[$tag][] = $block;
}
}
}
if (!$hidden) {
$this->append(array('block', $block), $s);
}
// this is done here so comments aren't bundled into he block that
// was just closed
$this->whitespace();
return true;
}
// mixin
if ($this->mixinTags($tags) &&
($this->argumentDef($argv, $isVararg) || true) &&
($this->keyword($suffix) || true) && $this->end())
{
$tags = $this->fixTags($tags);
$this->append(array('mixin', $tags, $argv, $suffix), $s);
return true;
} else {
$this->seek($s);
}
// spare ;
if ($this->literal(';')) return true;
return false; // got nothing, throw error
}
protected function isDirective($dirname, $directives) {
// TODO: cache pattern in parser
$pattern = implode("|",
array_map(array("lessc", "preg_quote"), $directives));
$pattern = '/^(-[a-z-]+-)?(' . $pattern . ')$/i';
return preg_match($pattern, $dirname);
}
protected function fixTags($tags) {
// move @ tags out of variable namespace
foreach ($tags as &$tag) {
if ($tag{0} == $this->lessc->vPrefix)
$tag[0] = $this->lessc->mPrefix;
}
return $tags;
}
// a list of expressions
protected function expressionList(&$exps) {
$values = array();
while ($this->expression($exp)) {
$values[] = $exp;
}
if (count($values) == 0) return false;
$exps = lessc::compressList($values, ' ');
return true;
}
/**
* Attempt to consume an expression.
* @link http://en.wikipedia.org/wiki/Operator-precedence_parser#Pseudo-code
*/
protected function expression(&$out) {
if ($this->value($lhs)) {
$out = $this->expHelper($lhs, 0);
// look for / shorthand
if (!empty($this->env->supressedDivision)) {
unset($this->env->supressedDivision);
$s = $this->seek();
if ($this->literal("/") && $this->value($rhs)) {
$out = array("list", "",
array($out, array("keyword", "/"), $rhs));
} else {
$this->seek($s);
}
}
return true;
}
return false;
}
/**
* recursively parse infix equation with $lhs at precedence $minP
*/
protected function expHelper($lhs, $minP) {
$this->inExp = true;
$ss = $this->seek();
while (true) {
$whiteBefore = isset($this->buffer[$this->count - 1]) &&
ctype_space($this->buffer[$this->count - 1]);
// If there is whitespace before the operator, then we require
// whitespace after the operator for it to be an expression
$needWhite = $whiteBefore && !$this->inParens;
if ($this->match(self::$operatorString.($needWhite ? '\s' : ''), $m) && self::$precedence[$m[1]] >= $minP) {
if (!$this->inParens && isset($this->env->currentProperty) && $m[1] == "/" && empty($this->env->supressedDivision)) {
foreach (self::$supressDivisionProps as $pattern) {
if (preg_match($pattern, $this->env->currentProperty)) {
$this->env->supressedDivision = true;
break 2;
}
}
}
$whiteAfter = isset($this->buffer[$this->count - 1]) &&
ctype_space($this->buffer[$this->count - 1]);
if (!$this->value($rhs)) break;
// peek for next operator to see what to do with rhs
if ($this->peek(self::$operatorString, $next) && self::$precedence[$next[1]] > self::$precedence[$m[1]]) {
$rhs = $this->expHelper($rhs, self::$precedence[$next[1]]);
}
$lhs = array('expression', $m[1], $lhs, $rhs, $whiteBefore, $whiteAfter);
$ss = $this->seek();
continue;
}
break;
}
$this->seek($ss);
return $lhs;
}
// consume a list of values for a property
public function propertyValue(&$value, $keyName = null) {
$values = array();
if ($keyName !== null) $this->env->currentProperty = $keyName;
$s = null;
while ($this->expressionList($v)) {
$values[] = $v;
$s = $this->seek();
if (!$this->literal(',')) break;
}
if ($s) $this->seek($s);
if ($keyName !== null) unset($this->env->currentProperty);
if (count($values) == 0) return false;
$value = lessc::compressList($values, ', ');
return true;
}
protected function parenValue(&$out) {
$s = $this->seek();
// speed shortcut
if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "(") {
return false;
}
$inParens = $this->inParens;
if ($this->literal("(") &&
($this->inParens = true) && $this->expression($exp) &&
$this->literal(")"))
{
$out = $exp;
$this->inParens = $inParens;
return true;
} else {
$this->inParens = $inParens;
$this->seek($s);
}
return false;
}
// a single value
protected function value(&$value) {
$s = $this->seek();
// speed shortcut
if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "-") {
// negation
if ($this->literal("-", false) &&
(($this->variable($inner) && $inner = array("variable", $inner)) ||
$this->unit($inner) ||
$this->parenValue($inner)))
{
$value = array("unary", "-", $inner);
return true;
} else {
$this->seek($s);
}
}
if ($this->parenValue($value)) return true;
if ($this->unit($value)) return true;
if ($this->color($value)) return true;
if ($this->func($value)) return true;
if ($this->string($value)) return true;
if ($this->keyword($word)) {
$value = array('keyword', $word);
return true;
}
// try a variable
if ($this->variable($var)) {
$value = array('variable', $var);
return true;
}
// unquote string (should this work on any type?
if ($this->literal("~") && $this->string($str)) {
$value = array("escape", $str);
return true;
} else {
$this->seek($s);
}
// css hack: \0
if ($this->literal('\\') && $this->match('([0-9]+)', $m)) {
$value = array('keyword', '\\'.$m[1]);
return true;
} else {
$this->seek($s);
}
return false;
}
// an import statement
protected function import(&$out) {
if (!$this->literal('@import')) return false;
// @import "something.css" media;
// @import url("something.css") media;
// @import url(something.css) media;
if ($this->propertyValue($value)) {
$out = array("import", $value);
return true;
}
}
protected function mediaQueryList(&$out) {
if ($this->genericList($list, "mediaQuery", ",", false)) {
$out = $list[2];
return true;
}
return false;
}
protected function mediaQuery(&$out) {
$s = $this->seek();
$expressions = null;
$parts = array();
if (($this->literal("only") && ($only = true) || $this->literal("not") && ($not = true) || true) && $this->keyword($mediaType)) {
$prop = array("mediaType");
if (isset($only)) $prop[] = "only";
if (isset($not)) $prop[] = "not";
$prop[] = $mediaType;
$parts[] = $prop;
} else {
$this->seek($s);
}
if (!empty($mediaType) && !$this->literal("and")) {
// ~
} else {
$this->genericList($expressions, "mediaExpression", "and", false);
if (is_array($expressions)) $parts = array_merge($parts, $expressions[2]);
}
if (count($parts) == 0) {
$this->seek($s);
return false;
}
$out = $parts;
return true;
}
protected function mediaExpression(&$out) {
$s = $this->seek();
$value = null;
if ($this->literal("(") &&
$this->keyword($feature) &&
($this->literal(":") && $this->expression($value) || true) &&
$this->literal(")"))
{
$out = array("mediaExp", $feature);
if ($value) $out[] = $value;
return true;
} elseif ($this->variable($variable)) {
$out = array('variable', $variable);
return true;
}
$this->seek($s);
return false;
}
// an unbounded string stopped by $end
protected function openString($end, &$out, $nestingOpen=null, $rejectStrs = null) {
$oldWhite = $this->eatWhiteDefault;
$this->eatWhiteDefault = false;
$stop = array("'", '"', "@{", $end);
$stop = array_map(array("lessc", "preg_quote"), $stop);
// $stop[] = self::$commentMulti;
if (!is_null($rejectStrs)) {
$stop = array_merge($stop, $rejectStrs);
}
$patt = '(.*?)('.implode("|", $stop).')';
$nestingLevel = 0;
$content = array();
while ($this->match($patt, $m, false)) {
if (!empty($m[1])) {
$content[] = $m[1];
if ($nestingOpen) {
$nestingLevel += substr_count($m[1], $nestingOpen);
}
}
$tok = $m[2];
$this->count-= strlen($tok);
if ($tok == $end) {
if ($nestingLevel == 0) {
break;
} else {
$nestingLevel--;
}
}
if (($tok == "'" || $tok == '"') && $this->string($str)) {
$content[] = $str;
continue;
}
if ($tok == "@{" && $this->interpolation($inter)) {
$content[] = $inter;
continue;
}
if (!empty($rejectStrs) && in_array($tok, $rejectStrs)) {
break;
}
$content[] = $tok;
$this->count+= strlen($tok);
}
$this->eatWhiteDefault = $oldWhite;
if (count($content) == 0) return false;
// trim the end
if (is_string(end($content))) {
$content[count($content) - 1] = rtrim(end($content));
}
$out = array("string", "", $content);
return true;
}
protected function string(&$out) {
$s = $this->seek();
if ($this->literal('"', false)) {
$delim = '"';
} elseif ($this->literal("'", false)) {
$delim = "'";
} else {
return false;
}
$content = array();
// look for either ending delim , escape, or string interpolation
$patt = '([^\n]*?)(@\{|\\\\|' .
lessc::preg_quote($delim).')';
$oldWhite = $this->eatWhiteDefault;
$this->eatWhiteDefault = false;
while ($this->match($patt, $m, false)) {
$content[] = $m[1];
if ($m[2] == "@{") {
$this->count -= strlen($m[2]);
if ($this->interpolation($inter, false)) {
$content[] = $inter;
} else {
$this->count += strlen($m[2]);
$content[] = "@{"; // ignore it
}
} elseif ($m[2] == '\\') {
$content[] = $m[2];
if ($this->literal($delim, false)) {
$content[] = $delim;
}
} else {
$this->count -= strlen($delim);
break; // delim
}
}
$this->eatWhiteDefault = $oldWhite;
if ($this->literal($delim)) {
$out = array("string", $delim, $content);
return true;
}
$this->seek($s);
return false;
}
protected function interpolation(&$out) {
$oldWhite = $this->eatWhiteDefault;
$this->eatWhiteDefault = true;
$s = $this->seek();
if ($this->literal("@{") &&
$this->openString("}", $interp, null, array("'", '"', ";")) &&
$this->literal("}", false))
{
$out = array("interpolate", $interp);
$this->eatWhiteDefault = $oldWhite;
if ($this->eatWhiteDefault) $this->whitespace();
return true;
}
$this->eatWhiteDefault = $oldWhite;
$this->seek($s);
return false;
}
protected function unit(&$unit) {
// speed shortcut
if (isset($this->buffer[$this->count])) {
$char = $this->buffer[$this->count];
if (!ctype_digit($char) && $char != ".") return false;
}
if ($this->match('([0-9]+(?:\.[0-9]*)?|\.[0-9]+)([%a-zA-Z]+)?', $m)) {
$unit = array("number", $m[1], empty($m[2]) ? "" : $m[2]);
return true;
}
return false;
}
// a # color
protected function color(&$out) {
if ($this->match('(#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3}))', $m)) {
if (strlen($m[1]) > 7) {
$out = array("string", "", array($m[1]));
} else {
$out = array("raw_color", $m[1]);
}
return true;
}
return false;
}
// consume an argument definition list surrounded by ()
// each argument is a variable name with optional value
// or at the end a ... or a variable named followed by ...
// arguments are separated by , unless a ; is in the list, then ; is the
// delimiter.
protected function argumentDef(&$args, &$isVararg) {
$s = $this->seek();
if (!$this->literal('(')) return false;
$values = array();
$delim = ",";
$method = "expressionList";
$isVararg = false;
while (true) {
if ($this->literal("...")) {
$isVararg = true;
break;
}
if ($this->$method($value)) {
if ($value[0] == "variable") {
$arg = array("arg", $value[1]);
$ss = $this->seek();
if ($this->assign() && $this->$method($rhs)) {
$arg[] = $rhs;
} else {
$this->seek($ss);
if ($this->literal("...")) {
$arg[0] = "rest";
$isVararg = true;
}
}
$values[] = $arg;
if ($isVararg) break;
continue;
} else {
$values[] = array("lit", $value);
}
}
if (!$this->literal($delim)) {
if ($delim == "," && $this->literal(";")) {
// found new delim, convert existing args
$delim = ";";
$method = "propertyValue";
// transform arg list
if (isset($values[1])) { // 2 items
$newList = array();
foreach ($values as $i => $arg) {
switch($arg[0]) {
case "arg":
if ($i) {
$this->throwError("Cannot mix ; and , as delimiter types");
}
$newList[] = $arg[2];
break;
case "lit":
$newList[] = $arg[1];
break;
case "rest":
$this->throwError("Unexpected rest before semicolon");
}
}
$newList = array("list", ", ", $newList);
switch ($values[0][0]) {
case "arg":
$newArg = array("arg", $values[0][1], $newList);
break;
case "lit":
$newArg = array("lit", $newList);
break;
}
} elseif ($values) { // 1 item
$newArg = $values[0];
}
if ($newArg) {
$values = array($newArg);
}
} else {
break;
}
}
}
if (!$this->literal(')')) {
$this->seek($s);
return false;
}
$args = $values;
return true;
}
// consume a list of tags
// this accepts a hanging delimiter
protected function tags(&$tags, $simple = false, $delim = ',') {
$tags = array();
while ($this->tag($tt, $simple)) {
$tags[] = $tt;
if (!$this->literal($delim)) break;
}
if (count($tags) == 0) return false;
return true;
}
// list of tags of specifying mixin path
// optionally separated by > (lazy, accepts extra >)
protected function mixinTags(&$tags) {
$tags = array();
while ($this->tag($tt, true)) {
$tags[] = $tt;
$this->literal(">");
}
if (count($tags) == 0) return false;
return true;
}
// a bracketed value (contained within in a tag definition)
protected function tagBracket(&$parts, &$hasExpression) {
// speed shortcut
if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "[") {
return false;
}
$s = $this->seek();
$hasInterpolation = false;
if ($this->literal("[", false)) {
$attrParts = array("[");
// keyword, string, operator
while (true) {
if ($this->literal("]", false)) {
$this->count--;
break; // get out early
}
if ($this->match('\s+', $m)) {
$attrParts[] = " ";
continue;
}
if ($this->string($str)) {
// escape parent selector, (yuck)
foreach ($str[2] as &$chunk) {
$chunk = str_replace($this->lessc->parentSelector, "$&$", $chunk);
}
$attrParts[] = $str;
$hasInterpolation = true;
continue;
}
if ($this->keyword($word)) {
$attrParts[] = $word;
continue;
}
if ($this->interpolation($inter, false)) {
$attrParts[] = $inter;
$hasInterpolation = true;
continue;
}
// operator, handles attr namespace too
if ($this->match('[|-~\$\*\^=]+', $m)) {
$attrParts[] = $m[0];
continue;
}
break;
}
if ($this->literal("]", false)) {
$attrParts[] = "]";
foreach ($attrParts as $part) {
$parts[] = $part;
}
$hasExpression = $hasExpression || $hasInterpolation;
return true;
}
$this->seek($s);
}
$this->seek($s);
return false;
}
// a space separated list of selectors
protected function tag(&$tag, $simple = false) {
if ($simple)
$chars = '^@,:;{}\][>\(\) "\'';
else
$chars = '^@,;{}["\'';
$s = $this->seek();
$hasExpression = false;
$parts = array();
while ($this->tagBracket($parts, $hasExpression));
$oldWhite = $this->eatWhiteDefault;
$this->eatWhiteDefault = false;
while (true) {
if ($this->match('(['.$chars.'0-9]['.$chars.']*)', $m)) {
$parts[] = $m[1];
if ($simple) break;
while ($this->tagBracket($parts, $hasExpression));
continue;
}
if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "@") {
if ($this->interpolation($interp)) {
$hasExpression = true;
$interp[2] = true; // don't unescape
$parts[] = $interp;
continue;
}
if ($this->literal("@")) {
$parts[] = "@";
continue;
}
}
if ($this->unit($unit)) { // for keyframes
$parts[] = $unit[1];
$parts[] = $unit[2];
continue;
}
break;
}
$this->eatWhiteDefault = $oldWhite;
if (!$parts) {
$this->seek($s);
return false;
}
if ($hasExpression) {
$tag = array("exp", array("string", "", $parts));
} else {
$tag = trim(implode($parts));
}
$this->whitespace();
return true;
}
// a css function
protected function func(&$func) {
$s = $this->seek();
if ($this->match('(%|[\w\-_][\w\-_:\.]+|[\w_])', $m) && $this->literal('(')) {
$fname = $m[1];
$sPreArgs = $this->seek();
$args = array();
while (true) {
$ss = $this->seek();
// this ugly nonsense is for ie filter properties
if ($this->keyword($name) && $this->literal('=') && $this->expressionList($value)) {
$args[] = array("string", "", array($name, "=", $value));
} else {
$this->seek($ss);
if ($this->expressionList($value)) {
$args[] = $value;
}
}
if (!$this->literal(',')) break;
}
$args = array('list', ',', $args);
if ($this->literal(')')) {
$func = array('function', $fname, $args);
return true;
} elseif ($fname == 'url') {
// couldn't parse and in url? treat as string
$this->seek($sPreArgs);
if ($this->openString(")", $string) && $this->literal(")")) {
$func = array('function', $fname, $string);
return true;
}
}
}
$this->seek($s);
return false;
}
// consume a less variable
protected function variable(&$name) {
$s = $this->seek();
if ($this->literal($this->lessc->vPrefix, false) &&
($this->variable($sub) || $this->keyword($name)))
{
if (!empty($sub)) {
$name = array('variable', $sub);
} else {
$name = $this->lessc->vPrefix.$name;
}
return true;
}
$name = null;
$this->seek($s);
return false;
}
/**
* Consume an assignment operator
* Can optionally take a name that will be set to the current property name
*/
protected function assign($name = null) {
if ($name) $this->currentProperty = $name;
return $this->literal(':') || $this->literal('=');
}
// consume a keyword
protected function keyword(&$word) {
if ($this->match('([\w_\-\*!"][\w\-_"]*)', $m)) {
$word = $m[1];
return true;
}
return false;
}
// consume an end of statement delimiter
protected function end() {
if ($this->literal(';', false)) {
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
return true;
} elseif ($this->count == strlen($this->buffer) || $this->buffer[$this->count] == '}') {
// if there is end of file or a closing block next then we don't need a ;
return true;
}
return false;
}
protected function guards(&$guards) {
$s = $this->seek();
if (!$this->literal("when")) {
$this->seek($s);
return false;
}
$guards = array();
while ($this->guardGroup($g)) {
$guards[] = $g;
if (!$this->literal(",")) break;
}
if (count($guards) == 0) {
$guards = null;
$this->seek($s);
return false;
}
return true;
}
// a bunch of guards that are and'd together
// TODO rename to guardGroup
protected function guardGroup(&$guardGroup) {
$s = $this->seek();
$guardGroup = array();
while ($this->guard($guard)) {
$guardGroup[] = $guard;
if (!$this->literal("and")) break;
}
if (count($guardGroup) == 0) {
$guardGroup = null;
$this->seek($s);
return false;
}
return true;
}
protected function guard(&$guard) {
$s = $this->seek();
$negate = $this->literal("not");
if ($this->literal("(") && $this->expression($exp) && $this->literal(")")) {
$guard = $exp;
if ($negate) $guard = array("negate", $guard);
return true;
}
$this->seek($s);
return false;
}
/* raw parsing functions */
protected function literal($what, $eatWhitespace = null) {
if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault;
// shortcut on single letter
if (!isset($what[1]) && isset($this->buffer[$this->count])) {
if ($this->buffer[$this->count] == $what) {
if (!$eatWhitespace) {
$this->count++;
return true;
}
// goes below...
} else {
return false;
}
}
if (!isset(self::$literalCache[$what])) {
self::$literalCache[$what] = lessc::preg_quote($what);
}
return $this->match(self::$literalCache[$what], $m, $eatWhitespace);
}
protected function genericList(&$out, $parseItem, $delim="", $flatten=true) {
$s = $this->seek();
$items = array();
while ($this->$parseItem($value)) {
$items[] = $value;
if ($delim) {
if (!$this->literal($delim)) break;
}
}
if (count($items) == 0) {
$this->seek($s);
return false;
}
if ($flatten && count($items) == 1) {
$out = $items[0];
} else {
$out = array("list", $delim, $items);
}
return true;
}
// advance counter to next occurrence of $what
// $until - don't include $what in advance
// $allowNewline, if string, will be used as valid char set
protected function to($what, &$out, $until = false, $allowNewline = false) {
if (is_string($allowNewline)) {
$validChars = $allowNewline;
} else {
$validChars = $allowNewline ? "." : "[^\n]";
}
if (!$this->match('('.$validChars.'*?)'.lessc::preg_quote($what), $m, !$until)) return false;
if ($until) $this->count -= strlen($what); // give back $what
$out = $m[1];
return true;
}
// try to match something on head of buffer
protected function match($regex, &$out, $eatWhitespace = null) {
if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault;
$r = '/'.$regex.($eatWhitespace && !$this->writeComments ? '\s*' : '').'/Ais';
if (preg_match($r, $this->buffer, $out, null, $this->count)) {
$this->count += strlen($out[0]);
if ($eatWhitespace && $this->writeComments) $this->whitespace();
return true;
}
return false;
}
// match some whitespace
protected function whitespace() {
if ($this->writeComments) {
$gotWhite = false;
while (preg_match(self::$whitePattern, $this->buffer, $m, null, $this->count)) {
if (isset($m[1]) && empty($this->seenComments[$this->count])) {
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
$this->append(array("comment", $m[1]));
$this->seenComments[$this->count] = true;
Support LESS stylesheets in ResourceLoader This patch adds support for the LESS stylesheet language to ResourceLoader. LESS is a stylesheet language that compiles into CSS. The patch includes lessphp, a LESS compiler implemented in PHP. The rationale for choosing LESS is explained in a MediaWiki RFC which accompanies this patch, available at <https://www.mediawiki.org/wiki/Requests_for_comment/LESS>. LESS support is provided for ResourceLoader file modules. It is triggered by the presence of the '.less' extension in stylesheet filenames. LESS files are compiled by lessc, and the resultant CSS is subjected to the standard set of transformations (CSSJanus & CSSMin). The immediate result of LESS compilation is encoded as an array, which includes the list of LESS files that were compiled and their mtimes. This array is cached. Cache invalidation is performed by comparing the cached mtimes with the mtimes of the files on disk. If the compiler itself throws an exception, ResourceLoader constructs a compilation result which consists of the error message encoded as a CSS comment. Failed compilation results are cached too, but with an expiration time of five minutes. The expiration time is required because the full list of referenced files is not known. Three configuration variables configure the global environment for LESS modules: $wgResourceLoaderLESSVars, $wgResourceLoaderLESSFunctions, and $wgResourceLoaderLESSImportPaths. $wgResourceLoaderLESSVars maps variable names to CSS values, specified as strings. Variables declared in this array are available in all LESS files. $wgResourceLoaderLESSFunctions is similar, except it maps custom function names to PHP callables. These functions can be called from within LESS to transform values. Read more about custom functions at <http://leafo.net/lessphp/docs/#custom_functions>. Finally, $wgResourceLoaderLESSImportPaths specifies file system paths in addition to the current module's path where the LESS compiler should look up files referenced in @import statements. The issue of handling of /* @embed */ and /* @noflip */ annotations is left unresolved. Earlier versions of this patch included an @embed analog implemented as a LESS custom function, but there was enough ambiguity about whether the strategy it took was optimal to merit discussing it in a separate, follow-up patch. Bug: 40964 Change-Id: Id052a04dd2f76a1f4aef39fbd454bd67f5fd282f
2013-08-11 16:40:20 +00:00
}
$this->count += strlen($m[0]);
$gotWhite = true;
}
return $gotWhite;
} else {
$this->match("", $m);
return strlen($m[0]) > 0;
}
}
// match something without consuming it
protected function peek($regex, &$out = null, $from=null) {
if (is_null($from)) $from = $this->count;
$r = '/'.$regex.'/Ais';
$result = preg_match($r, $this->buffer, $out, null, $from);
return $result;
}
// seek to a spot in the buffer or return where we are on no argument
protected function seek($where = null) {
if ($where === null) return $this->count;
else $this->count = $where;
return true;
}
/* misc functions */
public function throwError($msg = "parse error", $count = null) {
$count = is_null($count) ? $this->count : $count;
$line = $this->line +
substr_count(substr($this->buffer, 0, $count), "\n");
if (!empty($this->sourceName)) {
$loc = "$this->sourceName on line $line";
} else {
$loc = "line: $line";
}
// TODO this depends on $this->count
if ($this->peek("(.*?)(\n|$)", $m, $count)) {
throw new exception("$msg: failed at `$m[1]` $loc");
} else {
throw new exception("$msg: $loc");
}
}
protected function pushBlock($selectors=null, $type=null) {
$b = new stdclass;
$b->parent = $this->env;
$b->type = $type;
$b->id = self::$nextBlockId++;
$b->isVararg = false; // TODO: kill me from here
$b->tags = $selectors;
$b->props = array();
$b->children = array();
$this->env = $b;
return $b;
}
// push a block that doesn't multiply tags
protected function pushSpecialBlock($type) {
return $this->pushBlock(null, $type);
}
// append a property to the current block
protected function append($prop, $pos = null) {
if ($pos !== null) $prop[-1] = $pos;
$this->env->props[] = $prop;
}
// pop something off the stack
protected function pop() {
$old = $this->env;
$this->env = $this->env->parent;
return $old;
}
// remove comments from $text
// todo: make it work for all functions, not just url
protected function removeComments($text) {
$look = array(
'url(', '//', '/*', '"', "'"
);
$out = '';
$min = null;
while (true) {
// find the next item
foreach ($look as $token) {
$pos = strpos($text, $token);
if ($pos !== false) {
if (!isset($min) || $pos < $min[1]) $min = array($token, $pos);
}
}
if (is_null($min)) break;
$count = $min[1];
$skip = 0;
$newlines = 0;
switch ($min[0]) {
case 'url(':
if (preg_match('/url\(.*?\)/', $text, $m, 0, $count))
$count += strlen($m[0]) - strlen($min[0]);
break;
case '"':
case "'":
if (preg_match('/'.$min[0].'.*?(?<!\\\\)'.$min[0].'/', $text, $m, 0, $count))
$count += strlen($m[0]) - 1;
break;
case '//':
$skip = strpos($text, "\n", $count);
if ($skip === false) $skip = strlen($text) - $count;
else $skip -= $count;
break;
case '/*':
if (preg_match('/\/\*.*?\*\//s', $text, $m, 0, $count)) {
$skip = strlen($m[0]);
$newlines = substr_count($m[0], "\n");
}
break;
}
if ($skip == 0) $count += strlen($min[0]);
$out .= substr($text, 0, $count).str_repeat("\n", $newlines);
$text = substr($text, $count + $skip);
$min = null;
}
return $out.$text;
}
}
class lessc_formatter_classic {
public $indentChar = " ";
public $break = "\n";
public $open = " {";
public $close = "}";
public $selectorSeparator = ", ";
public $assignSeparator = ":";
public $openSingle = " { ";
public $closeSingle = " }";
public $disableSingle = false;
public $breakSelectors = false;
public $compressColors = false;
public function __construct() {
$this->indentLevel = 0;
}
public function indentStr($n = 0) {
return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));
}
public function property($name, $value) {
return $name . $this->assignSeparator . $value . ";";
}
protected function isEmpty($block) {
if (empty($block->lines)) {
foreach ($block->children as $child) {
if (!$this->isEmpty($child)) return false;
}
return true;
}
return false;
}
public function block($block) {
if ($this->isEmpty($block)) return;
$inner = $pre = $this->indentStr();
$isSingle = !$this->disableSingle &&
is_null($block->type) && count($block->lines) == 1;
if (!empty($block->selectors)) {
$this->indentLevel++;
if ($this->breakSelectors) {
$selectorSeparator = $this->selectorSeparator . $this->break . $pre;
} else {
$selectorSeparator = $this->selectorSeparator;
}
echo $pre .
implode($selectorSeparator, $block->selectors);
if ($isSingle) {
echo $this->openSingle;
$inner = "";
} else {
echo $this->open . $this->break;
$inner = $this->indentStr();
}
}
if (!empty($block->lines)) {
$glue = $this->break.$inner;
echo $inner . implode($glue, $block->lines);
if (!$isSingle && !empty($block->children)) {
echo $this->break;
}
}
foreach ($block->children as $child) {
$this->block($child);
}
if (!empty($block->selectors)) {
if (!$isSingle && empty($block->children)) echo $this->break;
if ($isSingle) {
echo $this->closeSingle . $this->break;
} else {
echo $pre . $this->close . $this->break;
}
$this->indentLevel--;
}
}
}
class lessc_formatter_compressed extends lessc_formatter_classic {
public $disableSingle = true;
public $open = "{";
public $selectorSeparator = ",";
public $assignSeparator = ":";
public $break = "";
public $compressColors = true;
public function indentStr($n = 0) {
return "";
}
}
class lessc_formatter_lessjs extends lessc_formatter_classic {
public $disableSingle = true;
public $breakSelectors = true;
public $assignSeparator = ": ";
public $selectorSeparator = ",";
}