Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

initial commit

  • Loading branch information...
commit f3af1a2a0644e5f8337d4a6087f084163e927555 0 parents
Konstantin Kudryashov authored June 21, 2010
4  .gitignore
... ...
@@ -0,0 +1,4 @@
  1
+README
  2
+package.xml
  3
+*.tgz
  4
+logo.*
3  .gitmodules
... ...
@@ -0,0 +1,3 @@
  1
+[submodule "lib/vendor/lessphp"]
  2
+	path = lib/vendor/lessphp
  3
+	url = http://github.com/leafo/lessphp.git
22  LICENSE
... ...
@@ -0,0 +1,22 @@
  1
+Copyright (c) 2010 Konstantin Kudryashov <ever.zet@gmail.com>
  2
+
  3
+Permission is hereby granted, free of charge, to any person
  4
+obtaining a copy of this software and associated documentation
  5
+files (the "Software"), to deal in the Software without
  6
+restriction, including without limitation the rights to use,
  7
+copy, modify, merge, publish, distribute, sublicense, and/or sell
  8
+copies of the Software, and to permit persons to whom the
  9
+Software is furnished to do so, subject to the following
  10
+conditions:
  11
+
  12
+The above copyright notice and this permission notice shall be
  13
+included in all copies or substantial portions of the Software.
  14
+
  15
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  16
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  17
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  18
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  19
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  20
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  21
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  22
+OTHER DEALINGS IN THE SOFTWARE.
160  README.markdown
Source Rendered
... ...
@@ -0,0 +1,160 @@
  1
+# sfLESSPlugin #
  2
+
  3
+*less.js in symfony.*
  4
+
  5
+sfLESSPlugin is a plugin for symfony applications. It's descendant of sfLessPhpPlugin, but with LESS2 compiler in mind.
  6
+
  7
+It can do most of `less.js` can:
  8
+
  9
+* Automatically parse your application `.less` files through LESS and outputs CSS files;
  10
+* Automatically include your application `.less` files into page source & enable browser-side LESS parsing.
  11
+
  12
+## LESS & less.js ##
  13
+
  14
+LESS extends CSS with: variables, mixins, operations and nested rules. For more information, see [http://lesscss.org](http://lesscss.org).
  15
+
  16
+less.js is LESS2. It's written on pure JavaScript & can be runned on both sides - in browser or in node.js.
  17
+
  18
+## Installation ##
  19
+
  20
+### Using symfony plugin:install ###
  21
+
  22
+Use this to install sfLESSPlugin:
  23
+
  24
+	$ symfony plugin:install sfLESSPlugin
  25
+
  26
+### Using git clone ###
  27
+
  28
+Use this to install as a plugin in a symfony app:
  29
+
  30
+	$ cd plugins && git clone git://github.com/everzet/sfLESSPlugin.git
  31
+
  32
+### Using git submodules ###
  33
+
  34
+Use this if you prefer to use git submodules for plugins:
  35
+
  36
+	$ git submodule add git://github.com/everzet/sfLESSPlugin.git plugins/sfLESSPlugin
  37
+	$ git submodule init
  38
+	$ git submodule update
  39
+
  40
+and enable plugin in your ProjectConfigurations class.
  41
+
  42
+## Usage ##
  43
+
  44
+After installation, you need to create directory `web/less`. Any LESS file placed in this directory, including subdirectories, will
  45
+automatically be parsed through LESS and saved as a corresponding CSS file in `web/css`. Example:
  46
+
  47
+	web/less/clients/screen.less => web/css/clients/screen.css
  48
+
  49
+If you prefix a file with an underscore, it is considered to be a partial, and will not be parsed unless included in another file. Example:
  50
+
  51
+	<file: web/less/clients/partials/_form.less>
  52
+	@text_dark: #222;
  53
+	
  54
+	<file: web/less/clients/screen.less>
  55
+	@import "partials/_form";
  56
+	
  57
+	input { color: @text_dark; }
  58
+
  59
+The example above will result in a single CSS file in `web/css/clients/screen.css`.
  60
+
  61
+## Setup ##
  62
+
  63
+sfLESSPlugin can use 2 workflows to manage your *.less files:
  64
+
  65
+1. Compile on browser side by `less.js`;
  66
+2. Compile on server side by `lessc`.
  67
+
  68
+### Compile on browser side ###
  69
+
  70
+This is default plugin behaviour. In this behaviour, all stylesheets, added in your `view.yml` configs, that ends with `.less`:
  71
+
  72
+	stylesheets:      [header/main.less]
  73
+
  74
+In this case, it will be automatically changed from something like
  75
+
  76
+	<link href="/css/header/main.less" media="screen" rel="stylesheet" type="text/css" />
  77
+
  78
+to
  79
+
  80
+	<link href="/less/header/main.less" media="screen" rel="stylesheet/less" type="text/css" />
  81
+
  82
+and will add link to `less.js` into javascripts list.
  83
+
  84
+This will cause browser to parse your linked less files on the fly.
  85
+
  86
+### Compile on server side ###
  87
+
  88
+In details, sfLESSPlugin server side compiler does the following:
  89
+
  90
+* Recursively looks for LESS (`.less`) files in `web/less`
  91
+* Ignores partials (prefixed with underscore: `_partial.less`) - these can be included with `@import` in your LESS files
  92
+* Saves the resulting CSS files to `web/css` using the same directory structure as `web/less`
  93
+
  94
+You have to install 2 packages:
  95
+
  96
+1. `node.js`;
  97
+2. `less.js`.
  98
+
  99
+After that, enable server behavior & disable browser behavior in `app.yml`:
  100
+
  101
+	sf_less_plugin:
  102
+	  compile:  true
  103
+	  use_js:   false
  104
+
  105
+In this case, sfLESSPlugin will try to find all your less files inside `web/less/*.less` & compile them into `web/css/*.css`, so you can link your less styles as default css stylesheets:
  106
+
  107
+	stylesheets:      [main.css]
  108
+
  109
+## Configuration ##
  110
+
  111
+sfLESSPlugin server side compiler rechecks `web/less/*.less` at every routes init. To prevent this, add this in your apps/APP/config/app.yml:
  112
+
  113
+	prod:
  114
+	  sf_less_plugin:
  115
+	    compile:  false
  116
+
  117
+sfLESSPlugin server side compiler checks the dates of LESS & CSS files, and will compile again only if LESS file have been changed since last parsing. To prevent this check & to enforce everytime compiling, add this in your apps/APP/config/app.yml:
  118
+
  119
+	dev:
  120
+	  sf_less_plugin:
  121
+	    check_dates:	false
  122
+
  123
+Also, sfLESSPlugin server side compiler has Web Debug Panel, from which you can view all styles to compile & can open them for edit in prefered editor. For that you need to configure sf_file_link_format in settings.yml.
  124
+
  125
+Last but not least, you can enable CSS compression (remove of whitespaces, tabs & newlines) in server side compiler with:
  126
+
  127
+	all:
  128
+	  sf_less_plugin:
  129
+	    use_compression:	true
  130
+
  131
+## Tasks ##
  132
+
  133
+sfLESSPlugin server side compiler provides a set of CLI tasks to help compiling your LESS styles.
  134
+
  135
+To parse all LESS files and save the resulting CSS files to the destination path, run:
  136
+
  137
+	$ symfony less:compile
  138
+
  139
+To delete all compiled CSS (only files, that been compiled from LESS files) files before parsing LESS, run:
  140
+
  141
+	$ symfony less:compile --clean
  142
+
  143
+If you want to compress CSS files after compilation, run:
  144
+
  145
+	$ symfony less:compile --compress
  146
+
  147
+Also, by default tasks don't use settings from app.yml such as "path". But you can specify application option to tell compiler from which app to get config:
  148
+
  149
+	$ symfony less:compile --application=frontend
  150
+
  151
+If you want to compile specific file, not all, just use argument & file name without ".less". To compile style.less into style.css, just run:
  152
+
  153
+	$ symfony less:compile style
  154
+
  155
+
  156
+## Contributors ##
  157
+
  158
+* everzet (lead): [http://github.com/everzet](http://github.com/everzet)
  159
+
  160
+less.js is maintained by Alexis Sellier [http://github.com/cloudhead](http://github.com/cloudhead)
56  config/sfLESSPluginConfiguration.class.php
... ...
@@ -0,0 +1,56 @@
  1
+<?php
  2
+
  3
+/*
  4
+ * This file is part of the sfLESSPlugin.
  5
+ * (c) 2010 Konstantin Kudryashov <ever.zet@gmail.com>
  6
+ *
  7
+ * For the full copyright and license information, please view the LICENSE
  8
+ * file that was distributed with this source code.
  9
+ */
  10
+
  11
+/**
  12
+ * sfLESSPluginConfiguration configures application to use LESS compiler.
  13
+ *
  14
+ * @package    sfLESSPlugin
  15
+ * @subpackage configuration
  16
+ * @author     Konstantin Kudryashov <ever.zet@gmail.com>
  17
+ * @version    1.0.0
  18
+ */
  19
+class sfLESSPluginConfiguration extends sfPluginConfiguration
  20
+{
  21
+  /**
  22
+   * @see sfPluginConfiguration
  23
+   */
  24
+  public function initialize()
  25
+  {
  26
+    // If sf_less_plugin_js in app.yml is set to true (by default)
  27
+    if (sfConfig::get('app_sf_less_plugin_use_js', true))
  28
+    {
  29
+      // Register listener to response.filter_content event
  30
+      $this->dispatcher->connect(
  31
+        'template.filter_parameters',
  32
+        array('sfLESS', 'findAndFixContentLinks')
  33
+      );
  34
+    }
  35
+
  36
+    // If sf_less_plugin_compile in app.yml is set to true
  37
+    if (sfConfig::get('app_sf_less_plugin_compile', false))
  38
+    {
  39
+      // Register listener to routing.load_configuration event
  40
+      $this->dispatcher->connect(
  41
+        'context.load_factories',
  42
+        array('sfLESS', 'findAndCompile')
  43
+      );
  44
+
  45
+      // If app_sf_less_plugin_toolbar in app.yml is set to true (by default)
  46
+      if (sfConfig::get('sf_web_debug') && sfConfig::get('app_sf_less_plugin_toolbar', true))
  47
+      {
  48
+        // Add LESS toolbar to Web Debug toolbar
  49
+        $this->dispatcher->connect('debug.web.load_panels', array(
  50
+          'sfWebDebugPanelLESS',
  51
+          'listenToLoadDebugWebPanelEvent'
  52
+        ));
  53
+      }
  54
+    }
  55
+  }
  56
+}
487  lib/sfLESS.class.php
... ...
@@ -0,0 +1,487 @@
  1
+<?php
  2
+
  3
+/*
  4
+ * This file is part of the sfLESSPlugin.
  5
+ * (c) 2010 Konstantin Kudryashov <ever.zet@gmail.com>
  6
+ *
  7
+ * For the full copyright and license information, please view the LICENSE
  8
+ * file that was distributed with this source code.
  9
+ */
  10
+
  11
+/**
  12
+ * sfLESS is helper class to provide LESS compiling in symfony projects.
  13
+ *
  14
+ * @package    sfLESSPlugin
  15
+ * @subpackage lib
  16
+ * @author     Konstantin Kudryashov <ever.zet@gmail.com>
  17
+ * @version    1.0.0
  18
+ */
  19
+class sfLESS
  20
+{
  21
+  /**
  22
+   * Array of LESS styles
  23
+   *
  24
+   * @var array
  25
+   **/
  26
+  protected static $results = array();
  27
+
  28
+  /**
  29
+   * Errors of compiler
  30
+   *
  31
+   * @var array
  32
+   **/
  33
+  protected static $errors  = array();
  34
+
  35
+  /**
  36
+   * Do we need to check dates before compile
  37
+   *
  38
+   * @var boolean
  39
+   */
  40
+  protected $checkDates     = true;
  41
+
  42
+  /**
  43
+   * Do we need compression for CSS files
  44
+   *
  45
+   * @var boolean
  46
+   */
  47
+  protected $useCompression = false;
  48
+
  49
+  /**
  50
+   * Current LESS file to be parsed. This var used to help output errors in callCompiler()
  51
+   *
  52
+   * @var string
  53
+   */
  54
+  protected $currentFile;
  55
+
  56
+  /**
  57
+   * Constructor
  58
+   *
  59
+   * @param   boolean $checkDates     Do we need to check dates before compile
  60
+   * @param   boolean $useCompression Do we need compression for CSS files
  61
+   */
  62
+  public function __construct($checkDates = true, $useCompression = false)
  63
+  {
  64
+    $this->setIsCheckDates($checkDates);
  65
+    $this->setIsUseCompression($useCompression);
  66
+  }
  67
+
  68
+  /**
  69
+   * Returns array of compiled styles info
  70
+   *
  71
+   * @return  array
  72
+   */
  73
+  public static function getCompileResults()
  74
+  {
  75
+    return self::$results;
  76
+  }
  77
+
  78
+  /**
  79
+   * Returns array of compiled styles errors
  80
+   *
  81
+   * @return  array
  82
+   */
  83
+  public static function getCompileErrors()
  84
+  {
  85
+    return self::$errors;
  86
+  }
  87
+
  88
+  /**
  89
+   * Returns debug info of the current state
  90
+   *
  91
+   * @return  array state
  92
+   */
  93
+  public function getDebugInfo()
  94
+  {
  95
+    return array(
  96
+      'dates'       => var_export($this->isCheckDates(), true),
  97
+      'compress'    => var_export($this->isUseCompression(), true),
  98
+      'less'        => $this->getLessPaths(),
  99
+      'css'         => $this->getCssPaths()
  100
+    );
  101
+  }
  102
+
  103
+  /**
  104
+   * Returns path with changed directory separators to unix-style (\ => /)
  105
+   *
  106
+   * @param   string  $path basic path
  107
+   * 
  108
+   * @return  string        unix-style path
  109
+   */
  110
+  public static function getSepFixedPath($path)
  111
+  {
  112
+    return str_replace(DIRECTORY_SEPARATOR, '/', $path);
  113
+  }
  114
+
  115
+  /**
  116
+   * Returns relative path from the project root dir
  117
+   *
  118
+   * @param   string  $fullPath full path to file
  119
+   * 
  120
+   * @return  string            relative path from the project root
  121
+   */
  122
+  public static function getProjectRelativePath($fullPath)
  123
+  {
  124
+    return str_replace(
  125
+      self::getSepFixedPath(sfConfig::get('sf_root_dir')) . '/',
  126
+      '',
  127
+      self::getSepFixedPath($fullPath)
  128
+    );
  129
+  }
  130
+
  131
+  /**
  132
+   * Do we need to check dates before compile
  133
+   *
  134
+   * @return  boolean
  135
+   */
  136
+  public function isCheckDates()
  137
+  {
  138
+    return sfConfig::get('app_sf_less_plugin_check_dates', $this->checkDates);
  139
+  }
  140
+
  141
+  /**
  142
+   * Set need of check dates before compile
  143
+   *
  144
+   * @param   boolean $checkDates Do we need to check dates before compile
  145
+   */
  146
+  public function setIsCheckDates($checkDates)
  147
+  {
  148
+    $this->checkDates = $checkDates;
  149
+  }
  150
+
  151
+  /**
  152
+   * Do we need compression for CSS files
  153
+   *
  154
+   * @return  boolean
  155
+   */
  156
+  public function isUseCompression()
  157
+  {
  158
+    return sfConfig::get('app_sf_less_plugin_use_compression', $this->useCompression);
  159
+  }
  160
+
  161
+  /**
  162
+   * Set need of compression for CSS files
  163
+   *
  164
+   * @param   boolean $useCompression Do we need compression for CSS files
  165
+   */
  166
+  public function setIsUseCompression($useCompression)
  167
+  {
  168
+    $this->useCompression = $useCompression;
  169
+  }
  170
+
  171
+  /**
  172
+   * Returns paths to CSS files
  173
+   *
  174
+   * @return  string  a path to CSS files directory
  175
+   */
  176
+  static public function getCssPaths()
  177
+  {  
  178
+    return self::getSepFixedPath(sfConfig::get('sf_web_dir')) . '/css/';
  179
+  }
  180
+
  181
+  /**
  182
+   * Returns all CSS files under the CSS directory
  183
+   *
  184
+   * @return  array   an array of CSS files
  185
+   */
  186
+  static public function findCssFiles()
  187
+  {
  188
+    return sfFinder::type('file')
  189
+      ->exec(array('sfLESS', 'isCssLessCompiled'))
  190
+      ->name('*.css')
  191
+      ->in(self::getCssPaths());
  192
+  }
  193
+
  194
+  /**
  195
+   * Returns header text for CSS files
  196
+   *
  197
+   * @return  string  a header text for CSS files
  198
+   */
  199
+  static protected function getCssHeader()
  200
+  {
  201
+    return '/* This CSS is autocompiled by LESS parser. Don\'t edit it manually. */';
  202
+  }
  203
+
  204
+  /**
  205
+   * Checks if CSS file was compiled from LESS
  206
+   *
  207
+   * @param   string  $dir    a path to file
  208
+   * @param   string  $entry  a filename
  209
+   * 
  210
+   * @return  boolean
  211
+   */
  212
+  static public function isCssLessCompiled($dir, $entry)
  213
+  {
  214
+    $file = $dir . '/' . $entry;
  215
+    $fp = fopen( $file, 'r' );
  216
+    $line = stream_get_line($fp, 1024, "\n");
  217
+    fclose($fp);
  218
+
  219
+    return (0 === strcmp($line, self::getCssHeader()));
  220
+  }
  221
+
  222
+  /**
  223
+   * Returns paths to LESS files
  224
+   *
  225
+   * @return  string  a path to LESS files directories
  226
+   */
  227
+  static public function getLessPaths()
  228
+  {
  229
+    return self::getSepFixedPath(sfConfig::get('sf_web_dir')) . '/less/';
  230
+  }
  231
+
  232
+  /**
  233
+   * Returns all LESS files under the LESS directories
  234
+   *
  235
+   * @return  array   an array of LESS files
  236
+   */
  237
+  static public function findLessFiles()
  238
+  {
  239
+    return sfFinder::type('file')
  240
+      ->name('*.less')
  241
+      ->discard('_*')
  242
+      ->follow_link()
  243
+      ->in(self::getLessPaths());
  244
+  }
  245
+
  246
+  /**
  247
+   * Returns CSS file path by its LESS alternative
  248
+   *
  249
+   * @param   string  $lessFile LESS file path
  250
+   * 
  251
+   * @return  string            CSS file path
  252
+   */
  253
+  static public function getCssPathOfLess($lessFile)
  254
+  {
  255
+    return str_replace(
  256
+      array(self::getLessPaths(), '.less'),
  257
+      array(self::getCssPaths(), '.css'),
  258
+      $lessFile
  259
+    );
  260
+  }
  261
+
  262
+  /**
  263
+   * Listens to template.filter_parameters & finds *.less links to fix it's rel & add less.js
  264
+   *
  265
+   * @param   sfEvent $event  an sfEvent instance
  266
+   * @param   string  $data   event data
  267
+   * 
  268
+   * @return  string          event data
  269
+   */
  270
+  static public function findAndFixContentLinks(sfEvent $event, $data)
  271
+  {
  272
+    $response = sfContext::getInstance()->getResponse();
  273
+    $hasLess  = false;
  274
+
  275
+    foreach ($response->getStylesheets() as $file => $options)
  276
+    {
  277
+      if ('.less' === substr($file, -5) && (!isset($options['rel']) || 'stylesheet/less' !== $options['rel']))
  278
+      {
  279
+        $response->removeStylesheet($file);
  280
+        $response->addStylesheet('/less/' . $file, '', array_merge(
  281
+          $options, array('rel' => 'stylesheet/less')
  282
+        ));
  283
+        $hasLess = true;
  284
+      }
  285
+    }
  286
+
  287
+    if ($hasLess)
  288
+    {
  289
+      $response->addJavascript(
  290
+        sfConfig::get('app_sf_less_plugin_js_lib', '/sfLESSPlugin/js/less-1.0.21.min.js')
  291
+      );
  292
+    }
  293
+
  294
+    return $data;
  295
+  }
  296
+
  297
+  /**
  298
+   * Listens to the routing.load_configuration event. Finds & compiles LESS files to CSS
  299
+   *
  300
+   * @param   sfEvent $event  an sfEvent instance
  301
+   */
  302
+  static public function findAndCompile(sfEvent $event)
  303
+  {
  304
+    // Start compilation timer for debug info
  305
+    $timer = sfTimerManager::getTimer('Less compilation');
  306
+
  307
+    // Create new helper object & compile LESS stylesheets with it
  308
+    $lessHelper = new self;
  309
+    foreach (self::findLessFiles() as $lessFile)
  310
+    {
  311
+      $lessHelper->compile($lessFile);
  312
+    }
  313
+
  314
+    // Stop timer
  315
+    $timer->addTime();
  316
+  }
  317
+
  318
+  /**
  319
+   * Compiles LESS file to CSS
  320
+   *
  321
+   * @param   string  $lessFile a LESS file
  322
+   * 
  323
+   * @return  boolean           true if succesfully compiled & false in other way
  324
+   */
  325
+  public function compile($lessFile)
  326
+  {
  327
+    // Creates timer
  328
+    $timer = new sfTimer;
  329
+
  330
+    // Gets CSS file path
  331
+    $cssFile = self::getCssPathOfLess($lessFile);
  332
+
  333
+    // Checks if path exists & create if not
  334
+    if (!is_dir(dirname($cssFile)))
  335
+    {
  336
+      mkdir(dirname($cssFile), 0777, true);
  337
+    }
  338
+
  339
+    // Is file compiled
  340
+    $isCompiled = false;
  341
+
  342
+    // If we check dates - recompile only really old CSS
  343
+    if ($this->isCheckDates())
  344
+    {
  345
+      if (!is_file($cssFile) || filemtime($lessFile) > filemtime($cssFile))
  346
+      {
  347
+        $isCompiled = $this->callCompiler($lessFile, $cssFile);
  348
+      }
  349
+    }
  350
+    else
  351
+    {
  352
+      $isCompiled = $this->callCompiler($lessFile, $cssFile);
  353
+    }
  354
+
  355
+    // Adds debug info to debug array
  356
+    self::$results[] = array(
  357
+      'lessFile'   => $lessFile,
  358
+      'cssFile'    => $cssFile,
  359
+      'compTime'   => $timer->getElapsedTime(),
  360
+      'isCompiled' => $isCompiled
  361
+    );
  362
+
  363
+    return $isCompiled;
  364
+  }
  365
+
  366
+  /**
  367
+   * Compress CSS by removing whitespaces, tabs, newlines, etc.
  368
+   *
  369
+   * @param   string  $css  CSS to be compressed
  370
+   * 
  371
+   * @return  string        compressed CSS
  372
+   */
  373
+  static public function getCompressedCss($css)
  374
+  {
  375
+    return str_replace(array("\r\n", "\r", "\n", "\t", '  ', '    ', '    '), '', $css);
  376
+  }
  377
+
  378
+  /**
  379
+   * Calls current LESS compiler for single file
  380
+   *
  381
+   * @param   string  $lessFile a LESS file
  382
+   * @param   string  $cssFile  a CSS file
  383
+   * 
  384
+   * @return  boolean           true if succesfully compiled & false in other way
  385
+   */
  386
+  public function callCompiler($lessFile, $cssFile)
  387
+  {
  388
+    // Setting current file. We will output this var if compiler throws error
  389
+    $this->currentFile = $lessFile;
  390
+
  391
+    // Call compiler
  392
+    $buffer = $this->callLesscCompiler($lessFile, $cssFile);
  393
+
  394
+    // Checks if compiler returns false
  395
+    if (false === $buffer)
  396
+    {
  397
+      return $buffer;
  398
+    }
  399
+
  400
+    // Compress CSS if we use compression
  401
+    if ($this->isUseCompression())
  402
+    {
  403
+      $buffer = self::getCompressedCss($buffer);
  404
+    }
  405
+
  406
+    // Add compiler header to CSS & writes it to file
  407
+    file_put_contents($cssFile, self::getCssHeader() . "\n\n" . $buffer);
  408
+
  409
+    // Setting current file to null
  410
+    $this->currentFile = null;
  411
+
  412
+    return true;
  413
+  }
  414
+
  415
+  /**
  416
+   * Calls lessc compiler for LESS file
  417
+   *
  418
+   * @param   string  $lessFile a LESS file
  419
+   * @param   string  $cssFile  a CSS file
  420
+   * 
  421
+   * @return  string            output
  422
+   */
  423
+  public function callLesscCompiler($lessFile, $cssFile)
  424
+  {
  425
+    // Compile with lessc
  426
+    $fs = new sfFilesystem;
  427
+    $command = sprintf('lessc "%s" "%s"', $lessFile, $cssFile);
  428
+
  429
+    if ('1.3.0' <= SYMFONY_VERSION)
  430
+    {
  431
+      try
  432
+      {
  433
+        $fs->execute($command, null, array($this, 'throwCompilerError'));
  434
+      }
  435
+      catch (RuntimeException $e)
  436
+      {
  437
+        return false;
  438
+      }
  439
+    }
  440
+    else
  441
+    {
  442
+      $fs->sh($command);
  443
+    }
  444
+
  445
+    return file_get_contents($cssFile);
  446
+  }
  447
+
  448
+  /**
  449
+   * Returns true if compiler can throw RuntimeException
  450
+   *
  451
+   * @return boolean
  452
+   */
  453
+  public function canThrowExceptions()
  454
+  {
  455
+    return (('prod' !== sfConfig::get('sf_environment') || !sfConfig::get('sf_app')) &&
  456
+        !(sfConfig::get('sf_web_debug') && sfConfig::get('app_sf_less_plugin_toolbar', true))
  457
+    );
  458
+  }
  459
+
  460
+  /**
  461
+   * Throws formatted compiler error
  462
+   *
  463
+   * @param   string  $line error line
  464
+   * 
  465
+   * @return  boolean
  466
+   */
  467
+  public function throwCompilerError($line)
  468
+  {
  469
+    // Generate error description
  470
+    $errorDescription = sprintf("LESS parser error in \"%s\":\n\n%s", $this->currentFile, $line);
  471
+
  472
+    // Adds error description to list of errors
  473
+    self::$errors[$this->currentFile] = $errorDescription;
  474
+
  475
+    // Throw exception if allowed & log error otherwise
  476
+    if ($this->canThrowExceptions())
  477
+    {
  478
+      throw new sfException($errorDescription);
  479
+    }
  480
+    else
  481
+    {
  482
+      sfContext::getInstance()->getLogger()->err($errorDescription);
  483
+    }
  484
+
  485
+    return false;
  486
+  }
  487
+}
143  lib/sfWebDebugPanelLESS.class.php
... ...
@@ -0,0 +1,143 @@
  1
+<?php
  2
+
  3
+/*
  4
+ * This file is part of the sfLESSPlugin.
  5
+ * (c) 2010 Konstantin Kudryashov <ever.zet@gmail.com>
  6
+ *
  7
+ * For the full copyright and license information, please view the LICENSE
  8
+ * file that was distributed with this source code.
  9
+ */
  10
+
  11
+/**
  12
+ * sfWebDebugPanelLESS implements LESS web debug panel.
  13
+ *
  14
+ * @package    sfLESSPlugin
  15
+ * @subpackage debug
  16
+ * @author     Konstantin Kudryashov <ever.zet@gmail.com>
  17
+ * @version    1.0.0
  18
+ */
  19
+class sfWebDebugPanelLESS extends sfWebDebugPanel
  20
+{
  21
+  /**
  22
+   * Listens to LoadDebugWebPanel event & adds this panel to the Web Debug toolbar
  23
+   *
  24
+   * @param   sfEvent $event
  25
+   */
  26
+  public static function listenToLoadDebugWebPanelEvent(sfEvent $event)
  27
+  {
  28
+    $event->getSubject()->setPanel(
  29
+      'documentation',
  30
+      new self($event->getSubject())
  31
+    );
  32
+  }
  33
+
  34
+  /**
  35
+   * @see sfWebDebugPanel
  36
+   */
  37
+  public function getTitle()
  38
+  {
  39
+    return '<img src="/sfLESSPlugin/images/css_go.png" alt="LESS helper" height="16" width="16" /> less';
  40
+  }
  41
+
  42
+  /**
  43
+   * @see sfWebDebugPanel
  44
+   */
  45
+  public function getPanelTitle()
  46
+  {
  47
+    return 'LESS Stylesheets';
  48
+  }
  49
+
  50
+  /**
  51
+   * @see sfWebDebugPanel
  52
+   */
  53
+  public function getPanelContent()
  54
+  {
  55
+    $panel = $this->getConfigurationContent() . '<table class="sfWebDebugLogs" style="width: 300px"><tr><th>less file</th><th>css file</th><th style="text-align:center;">time (ms)</th></tr>';
  56
+    $errorDescriptions = sfLESS::getCompileErrors();
  57
+    foreach (sfLESS::getCompileResults() as $info)
  58
+    {
  59
+      $info['error'] = isset($errorDescriptions[$info['lessFile']]) ? $errorDescriptions[$info['lessFile']] : false;
  60
+      $panel .= $this->getInfoContent($info);
  61
+    }
  62
+    $panel .= '</table>';
  63
+
  64
+    return $panel;
  65
+  }
  66
+
  67
+  /**
  68
+   * Returns configuration information for LESS compiler
  69
+   *
  70
+   * @return  string
  71
+   */
  72
+  protected function getConfigurationContent()
  73
+  {
  74
+    $debugInfo = '<dl id="less_debug" style="display: none;">';
  75
+    $lessHelper = new sfLESS;
  76
+    foreach ($lessHelper->getDebugInfo() as $name => $value)
  77
+    {
  78
+      $debugInfo .= sprintf('<dt style="float:left; width: 100px"><strong>%s:</strong></dt>
  79
+      <dd>%s</dd>', $name, $value);
  80
+    }
  81
+    $debugInfo .= '</dl>';
  82
+
  83
+    return sprintf(<<<EOF
  84
+      <h2>configuration %s</h2>
  85
+      %s<br/>
  86
+EOF
  87
+      ,$this->getToggler('less_debug', 'Toggle debug info')
  88
+      ,$debugInfo
  89
+    );
  90
+  }
  91
+
  92
+  /**
  93
+   * Returns information row for LESS style compilation
  94
+   *
  95
+   * @param   array   $info info of compilation process
  96
+   * @return  string
  97
+   */
  98
+  protected function getInfoContent($info, $error = false)
  99
+  {
  100
+    // ID of error row
  101
+    $errorId = md5($info['lessFile']);
  102
+
  103
+    // File link for preferred editor
  104
+    $fileLink = $this->formatFileLink(
  105
+      $info['lessFile'], 1, str_replace(sfLESS::getLessPaths(), '', $info['lessFile'])
  106
+    );
  107
+
  108
+    // Checking compile & error statuses
  109
+    if ($info['isCompiled'])
  110
+    {
  111
+      $trStyle = 'background-color:#a1d18d;';
  112
+    }
  113
+    elseif ($info['error'])
  114
+    {
  115
+      $this->setStatus(sfLogger::ERR);
  116
+      $trStyle = 'background-color:#f18c89;';
  117
+      $fileLink .= ' ' . $this->getToggler('less_error_' . $errorId, 'Toggle error info');
  118
+    }
  119
+    else
  120
+    {
  121
+      $trStyle = '';
  122
+    }
  123
+
  124
+    // Generating info rows
  125
+    $infoRows = sprintf(<<<EOF
  126
+      <tr style="%s">
  127
+        <td class="sfWebDebugLogType">%s</td>
  128
+        <td class="sfWebDebugLogType">%s</td>
  129
+        <td class="sfWebDebugLogNumber" style="text-align:center;">%.2f</td>
  130
+      </tr>
  131
+      <tr id="less_error_%s" style="display:none;background-color:#f18c89;"><td style="padding-left:15px" colspan="2">%s<td></tr>
  132
+EOF
  133
+      ,$trStyle
  134
+      ,$fileLink
  135
+      ,str_replace(sfLESS::getCssPaths(), '', $info['cssFile'])
  136
+      ,($info['isCompiled'] ? $info['compTime'] * 1000 : 0)
  137
+      ,$errorId
  138
+      ,$info['error']
  139
+    );
  140
+
  141
+    return $infoRows;
  142
+  }
  143
+}
118  lib/task/lessCompileTask.class.php
... ...
@@ -0,0 +1,118 @@
  1
+<?php
  2
+
  3
+/*
  4
+ * This file is part of the sfLESSPlugin.
  5
+ * (c) 2010 Konstantin Kudryashov <ever.zet@gmail.com>
  6
+ *
  7
+ * For the full copyright and license information, please view the LICENSE
  8
+ * file that was distributed with this source code.
  9
+ */
  10
+
  11
+/**
  12
+ * lessCompileTask compiles LESS files thru symfony cli task system.
  13
+ *
  14
+ * @package    sfLESSPlugin
  15
+ * @subpackage tasks
  16
+ * @author     Konstantin Kudryashov <ever.zet@gmail.com>
  17
+ * @version    1.0.0
  18
+ */
  19
+class lessCompileTask extends sfBaseTask
  20
+{
  21
+  /**
  22
+   * @see sfTask
  23
+   */
  24
+  protected function configure()
  25
+  {
  26
+    $this->addArguments(array(
  27
+      new sfCommandArgument('file', sfCommandArgument::OPTIONAL, 'LESS file to compile')
  28
+    ));
  29
+
  30
+    $this->addOptions(array(
  31
+      new sfCommandOption(
  32
+        'application',  null, sfCommandOption::PARAMETER_OPTIONAL,
  33
+        'The application name', null
  34
+      ),
  35
+      new sfCommandOption(
  36
+        'env',          null, sfCommandOption::PARAMETER_REQUIRED,
  37
+        'The environment', 'prod'
  38
+      ),
  39
+      new sfCommandOption(
  40
+        'clean',        null, sfCommandOption::PARAMETER_NONE,
  41
+        'Removing all compiled CSS in web/css before compile'
  42
+      ),
  43
+      new sfCommandOption(
  44
+        'compress',     null, sfCommandOption::PARAMETER_NONE,
  45
+        'Compress final CSS file'
  46
+      ),
  47
+      new sfCommandOption(
  48
+        'debug',        null, sfCommandOption::PARAMETER_NONE,
  49
+        'Outputs debug info'
  50
+      )
  51
+    ));
  52
+
  53
+    $this->namespace            = 'less';
  54
+    $this->name                 = 'compile';
  55
+    $this->briefDescription     = 'Recompiles LESS styles into web/css';
  56
+    $this->detailedDescription  = <<<EOF
  57
+The [less:compile|INFO] task recompiles LESS styles and puts compiled CSS into web/css folder.
  58
+Call it with:
  59
+
  60
+  [php symfony less:compile|INFO]
  61
+EOF;
  62
+  }
  63
+
  64
+  /**
  65
+   * @see sfTask
  66
+   */
  67
+  protected function execute($arguments = array(), $options = array())
  68
+  {
  69
+    // Remove old CSS files if --clean option specified
  70
+    if (isset($options['clean']) && $options['clean'])
  71
+    {
  72
+      foreach (sfLESS::findCssFiles() as $cssFile)
  73
+      {
  74
+        if (!isset($arguments['file']) || (false !== strpos($cssFile, $arguments['file'] . '.css')))
  75
+        {
  76
+          unlink($cssFile);
  77
+          $this->logSection('removed', str_replace(sfLESS::getCssPaths(), '', $cssFile));
  78
+        }
  79
+      }
  80
+    }
  81
+
  82
+    // Inits sfLESS instance for compilation help
  83
+    $lessHelper = new sfLESS(false, isset($options['compress']) && $options['compress']);
  84
+
  85
+    // Outputs debug info
  86
+    if (isset($options['debug']) && $options['debug'])
  87
+    {
  88
+      foreach ($lessHelper->getDebugInfo() as $key => $value)
  89
+      {
  90
+        $this->logSection('debug', sprintf("%s:\t%s", $key, $value), null, 'INFO');
  91
+      }
  92
+    }
  93
+
  94
+    // Compiles LESS files
  95
+    foreach (sfLESS::findLessFiles() as $lessFile)
  96
+    {
  97
+      if (!isset($arguments['file']) || (false !== strpos($lessFile, $arguments['file'] . '.less')))
  98
+      {
  99
+        if ($lessHelper->compile($lessFile))
  100
+        {
  101
+          if (isset($options['debug']) && $options['debug'])
  102
+          {
  103
+            $this->logSection('compiled', sprintf("%s => %s",
  104
+              sfLESS::getProjectRelativePath($lessFile),
  105
+              sfLESS::getProjectRelativePath(sfLESS::getCssPathOfLess($lessFile))
  106
+            ), null, 'COMMAND');
  107
+          }
  108
+          else
  109
+          {
  110
+            $this->logSection(
  111
+              'compiled', str_replace(sfLESS::getLessPaths(), '', $lessFile), null, 'COMMAND'
  112
+            );
  113
+          }
  114
+        }
  115
+      }
  116
+    }
  117
+  }
  118
+}
BIN  web/images/css_go.png
60  web/js/less-1.0.21.min.js
... ...
@@ -0,0 +1,60 @@
  1
+//
  2
+// LESS - Leaner CSS v1.0.21
  3
+// http://lesscss.org
  4
+// 
  5
+// Copyright (c) 2010, Alexis Sellier
  6
+// Licensed under the Apache 2.0 License.
  7
+//
  8
+(function(u){function q(b){return u.less[b.split("/")[1]]}function O(b){if(!document.querySelectorAll&&typeof jQuery==="undefined")x("no selector method found.");else return(document.querySelectorAll||jQuery).call(document,b)}function K(b,a){for(var d=0;d<G.length;d++)L(G[d],b,a,G.length-(d+1))}function L(b,a,d,f){var e=b.href.replace(/\?.*$/,""),i=y&&y.getItem(e),j=y&&y.getItem(e+":timestamp"),k={css:i,timestamp:j};P(b.href,function(o,r){if(!d&&k&&(new Date(r)).valueOf()===(new Date(k.timestamp)).valueOf()){D(k.css,
  9
+b);a(null,b,{local:true,remaining:f})}else(new p.Parser({optimization:p.optimization,paths:[e.replace(/[\w\.-]+$/,"")]})).parse(o,function(t,s){if(t)return M(t,e);try{a(s,b,{local:false,lastModified:r,remaining:f});Q(document.getElementById("less-error-message:"+e.replace(/[^a-z]+/gi,"-")))}catch(E){M(E,e)}})},function(o,r){throw new Error("Couldn't load "+r+" ("+o+")");})}function D(b,a,d){var f,e=a.href?a.href.replace(/\?.*$/,""):"";a="less:"+(a.title||e.match(/(?:^|\/)([-\w]+)\.[a-z]+$/i)[1]);
  10
+if((f=document.getElementById(a))===null){f=document.createElement("style");f.type="text/css";f.media="screen";f.id=a;document.getElementsByTagName("head")[0].appendChild(f)}if(f.styleSheet)try{f.styleSheet.cssText=b}catch(i){throw new Error("Couldn't reassign styleSheet.cssText.");}else(function(j){if(f.childNodes.length>0)f.firstChild.nodeValue!==j.nodeValue&&f.replaceChild(j,f.firstChild);else f.appendChild(j)})(document.createTextNode(b));if(d&&y){x("saving "+e+" to cache.");y.setItem(e,b);y.setItem(e+
  11
+":timestamp",d)}}function P(b,a,d){function f(j,k,o){if(j.status>=200&&j.status<300)k(j.responseText,j.getResponseHeader("Last-Modified"));else typeof o==="function"&&o(j.status,b)}var e=R(),i=F?false:p.async;e.open("GET",b,i);e.send(null);if(F)e.status===0?a(e.responseText):d(e.status);else if(i)e.onreadystatechange=function(){e.readyState==4&&f(e,a,d)};else f(e,a,d)}function R(){if(u.XMLHttpRequest)return new XMLHttpRequest;else try{return new ActiveXObject("MSXML2.XMLHTTP.3.0")}catch(b){x("browser doesn't support AJAX.");
  12
+return null}}function Q(b){return b&&b.parentNode.removeChild(b)}function x(b){p.env=="development"&&typeof console!=="undefined"&&console.log("less: "+b)}function M(b,a){var d="less-error-message:"+a.replace(/[^a-z]+/ig,"-");if(!b.extract)throw b;var f=document.createElement("div"),e;f.id=d;f.className="less-error-message";f.innerHTML="<h3>"+(b.message||"There is an error in your .less file")+'</h3><p><a href="'+a+'">'+a+"</a> on line "+b.line+", column "+(b.column+1)+":</p>"+'<div>\n<pre class="ctx"><span>[-1]</span>{0}</pre>\n<pre><span>[0]</span>{current}</pre>\n<pre class="ctx"><span>[1]</span>{2}</pre>\n</div>'.replace(/\[(-?\d)\]/g,
  13
+function(i,j){return parseInt(b.line)+parseInt(j)||""}).replace(/\{(\d)\}/g,function(i,j){return b.extract[parseInt(j)]||""}).replace(/\{current\}/,b.extract[1].slice(0,b.column)+'<span class="error">'+b.extract[1].slice(b.column)+"</span>");D(".less-error-message span {\nmargin-right: 15px;\n}\n.less-error-message pre {\ncolor: #ee4444;\npadding: 4px 0;\nmargin: 0;\n}\n.less-error-message pre.ctx {\ncolor: #dd7777;\n}\n.less-error-message h3 {\npadding: 15px 0 5px 0;\nmargin: 0;\n}\n.less-error-message a {\ncolor: #10a\n}\n.less-error-message .error {\ncolor: red;\nfont-weight: bold;\npadding-bottom: 2px;\nborder-bottom: 1px dashed red;\n}",
  14
+{title:"error-message"});f.style.cssText="font-family: Arial, sans-serif;border: 1px solid #e00;background-color: #eee;border-radius: 5px;-webkit-border-radius: 5px;-moz-border-radius: 5px;color: #e00;padding: 15px;margin-bottom: 15px";if(p.env=="development")e=setInterval(function(){if(document.body){document.getElementById(d)?document.body.replaceChild(f,document.getElementById(d)):document.body.insertBefore(f,document.body.firstChild);clearInterval(e)}},10)}if(!Array.isArray)Array.isArray=function(b){return Object.prototype.toString.call(b)===
  15
+"[object Array]"||b instanceof Array};if(!Array.prototype.forEach)Array.prototype.forEach=function(b,a){for(var d=this.length>>>0,f=0;f<d;f++)f in this&&b.call(a,this[f],f,this)};if(!Array.prototype.map)Array.prototype.map=function(b,a){for(var d=this.length>>>0,f=new Array(d),e=0;e<d;e++)if(e in this)f[e]=b.call(a,this[e],e,this);return f};if(!Array.prototype.filter)Array.prototype.filter=function(b,a){for(var d=[],f=0;f<this.length;f++)b.call(a,this[f])&&d.push(this[f]);return d};if(!Array.prototype.reduce)Array.prototype.reduce=
  16
+function(b){var a=this.length>>>0,d=0;if(a===0&&arguments.length===1)throw new TypeError;if(arguments.length>=2)var f=arguments[1];else{do{if(d in this){f=this[d++];break}if(++d>=a)throw new TypeError;}while(1)}for(;d<a;d++)if(d in this)f=b.call(null,f,this[d],d,this);return f};if(!Array.prototype.indexOf)Array.prototype.indexOf=function(b,a){var d=this.length;a=a||0;if(!d)return-1;if(a>=d)return-1;if(a<0)a+=d;for(;a<d;a++)if(Object.prototype.hasOwnProperty.call(this,a))if(b===this[a])return a;return-1};
  17
+if(!Object.keys)Object.keys=function(b){var a=[];for(var d in b)Object.prototype.hasOwnProperty.call(b,d)&&a.push(d);return a};if(!String.prototype.trim)String.prototype.trim=function(){return String(this).replace(/^\s\s*/,"").replace(/\s\s*$/,"")};var p,m;if(typeof u==="undefined"){p=exports;m=q("less/tree")}else{p=u.less={};m=u.less.tree={}}p.Parser=function(b){function a(g){var h,l,n;if(g instanceof Function)return g.call(r.parsers);else if(typeof g==="string"){h=f.charAt(e)===g?g:null;l=1}else{if(e>=
  18
+o+k[i].length&&i<k.length-1)o+=k[i++].length;g.lastIndex=n=e-o;if(h=g.exec(k[i])){l=h[0].length;if(g.lastIndex-l!==n)return}}if(h){e+=l;for(l=o+k[i].length;e<=l;){g=f.charCodeAt(e);if(!(g===32||g===10||g===9))break;e++}return typeof h==="string"?h:h.length===1?h[0]:h}}function d(g){var h;if(typeof g==="string")return f.charAt(e)===g;else{g.lastIndex=e;if((h=g.exec(f))&&g.lastIndex-h[0].length===e)return h}}var f,e,i,j,k,o,r,t=this,s=function(){},E=this.imports={paths:b&&b.paths||[],queue:[],files:{},
  19
+push:function(g,h){var l=this;this.queue.push(g);p.Parser.importer(g,this.paths,function(n){l.queue.splice(l.queue.indexOf(g),1);l.files[g]=n;h(n);l.queue.length===0&&s()})}};this.env=b||{};this.optimization="optimization"in this.env?this.env.optimization:1;return r={imports:E,parse:function(g,h){var l,n,z=null;e=i=o=j=0;k=[];f=g.replace(/\r\n/g,"\n");if(t.optimization>0){f=f.replace(/\/\*(?:[^*]|\*+[^\/*])*\*+\//g,function(H){return t.optimization>1?"":H.replace(/\n(\s*\n)+/g,"\n")});k=f.split(/^(?=\n)/mg)}else k=
  20
+[f];l=new m.Ruleset([],a(this.parsers.primary));l.root=true;l.toCSS=function(H){var A,B;return function(v){function I(S){return(f.slice(0,S).match(/\n/g)||"").length}v=v||{};try{var C=H.call(this,[],{frames:[],compress:v.compress||false});return v.compress?C.replace(/(\s)+/g,"$1"):C}catch(w){B=f.split("\n");A=I(w.index);v=w.index;for(C=-1;v>=0&&f.charAt(v)!=="\n";v--)C++;throw{name:"NameError",message:w.message,filename:b.filename,index:w.index,line:A+1,callLine:w.call&&I(w.call)+1,callExtract:B[I(w.call)-
  21
+1],stack:w.stack,column:C,extract:[B[A-1],B[A],B[A+1]]};}}}(l.toCSS);if(e<f.length-1){e=j;n=f.split("\n");g=(f.slice(0,e).match(/\n/g)||"").length+1;for(var J=e,N=-1;J>=0&&f.charAt(J)!=="\n";J--)N++;z={name:"ParseError",message:"Syntax Error on line "+g,filename:b.filename,line:g,column:N,extract:[n[g-2],n[g-1],n[g]]}}if(this.imports.queue.length>0)s=function(){h(z,l)};else h(z,l)},parsers:{primary:function(){for(var g,h=[];g=a(this.mixin.definition)||a(this.rule)||a(this.ruleset)||a(this.mixin.call)||
  22
+a(this.comment)||a(/[\n\s]+/g)||a(this.directive);)h.push(g);return h},comment:function(){var g;if(f.charAt(e)==="/")if(f.charAt(e+1)==="/")return new m.Comment(a(/\/\/.*/g),true);else if(g=a(/\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/g))return new m.Comment(g)},entities:{quoted:function(){var g;if(!(f.charAt(e)!=='"'&&f.charAt(e)!=="'"))if(g=a(/"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/g))return new m.Quoted(g[0],g[1]||g[2])},keyword:function(){var g;if(g=a(/[A-Za-z-]+/g))return new m.Keyword(g)},call:function(){var g,
  23
+h;if(g=a(/([\w-]+|%)\(/g)){if(g[1].toLowerCase()==="alpha")return a(this.alpha);h=a(this.entities.arguments);if(a(")"))if(g)return new m.Call(g[1],h)}},arguments:function(){for(var g=[],h;h=a(this.expression);){g.push(h);if(!a(","))break}return g},literal:function(){return a(this.entities.dimension)||a(this.entities.color)||a(this.entities.quoted)},url:function(){var g;if(!(f.charAt(e)!=="u"||!a(/url\(/g))){g=a(this.entities.quoted)||a(/[-\w%@$\/.&=:;#+?]+/g);if(!a(")"))throw new Error("missing closing ) for url()");
  24
+return new m.URL(g.value?g:new m.Anonymous(g))}},variable:function(){var g,h=e;if(f.charAt(e)==="@"&&(g=a(/@[\w-]+/g)))return new m.Variable(g,h)},color:function(){var g;if(f.charAt(e)==="#"&&(g=a(/#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})/g)))return new m.Color(g[1])},dimension:function(){var g;g=f.charCodeAt(e);if(!(g>57||g<45||g===47))if(g=a(/(-?\d*\.?\d+)(px|%|em|pc|ex|in|deg|s|ms|pt|cm|mm)?/g))return new m.Dimension(g[1],g[2])}},variable:function(){var g;if(f.charAt(e)==="@"&&(g=a(/(@[\w-]+)\s*:/g)))return g[1]},
  25
+shorthand:function(){var g,h;if(d(/[@\w.-]+\/[@\w.-]+/g))if((g=a(this.entity))&&a("/")&&(h=a(this.entity)))return new m.Shorthand(g,h)},mixin:{call:function(){for(var g=[],h,l,n,z=e;h=a(/[#.][\w-]+/g);){g.push(new m.Element(l,h));l=a(">")}a("(")&&(n=a(this.entities.arguments))&&a(")");if(g.length>0&&(a(";")||d("}")))return new m.mixin.Call(g,n,z)},definition:function(){var g,h=[],l,n;if(!(f.charAt(e)!=="."||d(/[^{]*(;|})/g)))if(g=a(/([#.][\w-]+)\s*\(/g)){for(g=g[1];l=a(/@[\w-]+/g)||a(this.entities.literal)||
  26
+a(this.entities.keyword);){if(l[0]==="@")if(a(":"))if(n=a(this.expression))h.push({name:l,value:n});else throw new Error("Expected value");else h.push({name:l});else h.push({value:l});if(!a(","))break}if(!a(")"))throw new Error("Expected )");if(l=a(this.block))return new m.mixin.Definition(g,h,l)}}},entity:function(){return a(this.entities.literal)||a(this.entities.variable)||a(this.entities.url)||a(this.entities.call)||a(this.entities.keyword)},end:function(){return a(";")||d("}")},alpha:function(){var g;
  27
+if(a(/opacity=/gi))if(g=a(/\d+/g)||a(this.entities.variable)){if(!a(")"))throw new Error("missing closing ) for alpha()");return new m.Alpha(g)}},element:function(){var g;c=a(this.combinator);if(g=a(/[.#:]?[\w-]+/g)||a("*")||a(this.attribute)||a(/\([^)@]+\)/g))return new m.Element(c,g)},combinator:function(){var g;return(g=a(/[+>~]/g)||a("&")||a(/::/g))?new m.Combinator(g):new m.Combinator(f.charAt(e-1)===" "?" ":null)},selector:function(){for(var g,h=[];g=a(this.element);)h.push(g);if(h.length>0)return new m.Selector(h)},
  28
+tag:function(){return a(/[a-zA-Z][a-zA-Z-]*[0-9]?/g)||a("*")},attribute:function(){var g="",h,l,n;if(a("[")){if(h=a(/[a-z-]+/g)||a(this.entities.quoted))g=(n=a(/[|~*$^]?=/g))&&(l=a(this.entities.quoted)||a(/[\w-]+/g))?[h,n,l.toCSS?l.toCSS():l].join(""):h;if(a("]"))if(g)return"["+g+"]"}},block:function(){var g;if(a("{")&&(g=a(this.primary))&&a("}"))return g},ruleset:function(){var g=[],h,l,n=e;if(h=d(/([.#: \w-]+)[\s\n]*\{/g)){e+=h[0].length-1;g=[new m.Selector([new m.Element(null,h[1])])]}else{for(;h=
  29
+a(this.selector);){g.push(h);if(!a(","))break}h&&a(this.comment)}if(g.length>0&&(l=a(this.block)))return new m.Ruleset(g,l);else{j=e;e=n}},rule:function(){var g,h=e;if(name=a(this.property)||a(this.variable)){if(name.charAt(0)!="@"&&(match=d(/([^@+\/*(;{}-]*);/g))){e+=match[0].length-1;g=new m.Anonymous(match[1])}else g=name==="font"?a(this.font):a(this.value);if(a(this.end))return new m.Rule(name,g,h);else{j=e;e=h}}},"import":function(){var g;if(a(/@import\s+/g)&&(g=a(this.entities.quoted)||a(this.entities.url))&&
  30
+a(";"))return new m.Import(g,E)},directive:function(){var g,h,l;if(f.charAt(e)==="@")if(h=a(this["import"]))return h;else if(g=a(/@media|@page/g)){l=a(/[^{]+/g).trim();if(h=a(this.block))return new m.Directive(g+" "+l,h)}else if(g=a(/@[-a-z]+/g))if(g==="@font-face"){if(h=a(this.block))return new m.Directive(g,h)}else if((h=a(this.entity))&&a(";"))return new m.Directive(g,h)},font:function(){for(var g=[],h=[],l;l=a(this.shorthand)||a(this.entity);)h.push(l);g.push(new m.Expression(h));if(a(","))for(;l=
  31
+a(this.expression);){g.push(l);if(!a(","))break}return new m.Value(g,a(this.important))},value:function(){for(var g,h=[];g=a(this.expression);){h.push(g);if(!a(","))break}g=a(this.important);if(h.length>0)return new m.Value(h,g)},important:function(){return a(/!\s*important/g)},sub:function(){var g;if(a("(")&&(g=a(this.expression))&&a(")"))return g},multiplication:function(){var g,h,l,n;if(g=a(this.operand)){for(;(l=a(/[\/*]/g))&&(h=a(this.operand));)n=new m.Operation(l,[n||g,h]);return n||g}},addition:function(){var g,
  32
+h,l,n;if(g=a(this.multiplication)){for(;(l=a(/[-+]\s+/g)||f.charAt(e-1)!=" "&&a(/[-+]/g))&&(h=a(this.multiplication));)n=new m.Operation(l,[n||g,h]);return n||g}},operand:function(){return a(this.sub)||a(this.entities.dimension)||a(this.entities.color)||a(this.entities.variable)},expression:function(){for(var g,h=[];g=a(this.addition)||a(this.entity);)h.push(g);if(h.length>0)return new m.Expression(h)},property:function(){var g;if(g=a(/(\*?-?[-a-z_0-9]+)\s*:/g))return g[1]}}}};if(typeof u!=="undefined")p.Parser.importer=
  33
+function(b,a,d){if(b[0]!=="/"&&a.length>0)b=a[0]+b;L({href:b,title:b},function(f){d(f)})};(function(b){function a(e){return b.functions.hsla(e.h,e.s,e.l,e.a)}function d(e){if(e instanceof b.Dimension)return parseFloat(e.unit=="%"?e.value/100:e.value);else if(typeof e==="number")return e;else throw{error:"RuntimeError",message:"color functions take numbers as parameters"};}function f(e){return Math.min(1,Math.max(0,e))}b.functions={rgb:function(e,i,j){return this.rgba(e,i,j,1)},rgba:function(e,i,j,
  34
+k){e=[e,i,j].map(function(o){return d(o)});k=d(k);return new b.Color(e,k)},hsl:function(e,i,j){return this.hsla(e,i,j,1)},hsla:function(e,i,j,k){function o(s){s=s<0?s+1:s>1?s-1:s;return s*6<1?t+(r-t)*s*6:s*2<1?r:s*3<2?t+(r-t)*(2/3-s)*6:t}e=d(e)%360/360;i=d(i);j=d(j);k=d(k);var r=j<=0.5?j*(i+1):j+i-j*i,t=j*2-r;return this.rgba(o(e+1/3)*255,o(e)*255,o(e-1/3)*255,k)},hue:function(e){return new b.Dimension(Math.round(e.toHSL().h))},saturation:function(e){return new b.Dimension(Math.round(e.toHSL().s*
  35
+100),"%")},lightness:function(e){return new b.Dimension(Math.round(e.toHSL().l*100),"%")},alpha:function(e){return new b.Dimension(e.toHSL().a)},saturate:function(e,i){e=e.toHSL();e.s+=i.value/100;e.s=f(e.s);return a(e)},desaturate:function(e,i){e=e.toHSL();e.s-=i.value/100;e.s=f(e.s);return a(e)},lighten:function(e,i){e=e.toHSL();e.l+=i.value/100;e.l=f(e.l);return a(e)},darken:function(e,i){e=e.toHSL();e.l-=i.value/100;e.l=f(e.l);return a(e)},spin:function(e,i){e=e.toHSL();i=(e.h+i.value)%360;e.h=
  36
+i<0?360+i:i;return a(e)},greyscale:function(e){return this.desaturate(e,new b.Dimension(100))},e:function(e){return new b.Anonymous(e)},"%":function(e){for(var i=Array.prototype.slice.call(arguments,1),j=e.content,k=0;k<i.length;k++)j=j.replace(/%s/,i[k].content).replace(/%[da]/,i[k].toCSS());j=j.replace(/%%/g,"%");return new b.Quoted('"'+j+'"',j)}}})(q("less/tree"));(function(b){b.Alpha=function(a){this.value=a};b.Alpha.prototype={toCSS:function(){return"alpha(opacity="+this.value.toCSS()+")"},eval:function(){return this}}})(q("less/tree"));
  37
+(function(b){b.Anonymous=function(a){this.value=a.content||a};b.Anonymous.prototype={toCSS:function(){return this.value},eval:function(){return this}}})(q("less/tree"));(function(b){b.Call=function(a,d){this.name=a;this.args=d};b.Call.prototype={eval:function(a){var d=this.args.map(function(f){return f.eval(a)});return this.name in b.functions?b.functions[this.name].apply(b.functions,d):new b.Anonymous(this.name+"("+d.map(function(f){return f.toCSS()}).join(", ")+")")},toCSS:function(a){return this.eval(a).toCSS()}}})(q("less/tree"));
  38
+(function(b){b.Color=function(a,d){this.rgb=Array.isArray(a)?a:a.length==6?a.match(/.{2}/g).map(function(f){return parseInt(f,16)}):a.split("").map(function(f){return parseInt(f+f,16)});this.alpha=typeof d==="number"?d:1};b.Color.prototype={eval:function(){return this},toCSS:function(){return this.alpha<1?"rgba("+this.rgb.map(function(a){return Math.round(a)}).concat(this.alpha).join(", ")+")":"#"+this.rgb.map(function(a){a=Math.round(a);a=(a>255?255:a<0?0:a).toString(16);return a.length===1?"0"+
  39
+a:a}).join("")},operate:function(a,d){var f=[];d instanceof b.Color||(d=d.toColor());for(var e=0;e<3;e++)f[e]=b.operate(a,this.rgb[e],d.rgb[e]);return new b.Color(f)},toHSL:function(){var a=this.rgb[0]/255,d=this.rgb[1]/255,f=this.rgb[2]/255,e=this.alpha,i=Math.max(a,d,f),j=Math.min(a,d,f),k,o=(i+j)/2,r=i-j;if(i===j)k=j=0;else{j=o>0.5?r/(2-i-j):r/(i+j);switch(i){case a:k=(d-f)/r+(d<f?6:0);break;case d:k=(f-a)/r+2;break;case f:k=(a-d)/r+4;break}k/=6}return{h:k*360,s:j,l:o,a:e}}}})(q("less/tree"));
  40
+(function(b){b.Comment=function(a,d){this.value=a;this.silent=!!d};b.Comment.prototype={toCSS:function(a){return a.compress?"":this.value},eval:function(){return this}}})(q("less/tree"));(function(b){b.Dimension=function(a,d){this.value=parseFloat(a);this.unit=d||null};b.Dimension.prototype={eval:function(){return this},toColor:function(){return new b.Color([this.value,this.value,this.value])},toCSS:function(){return this.value+this.unit},operate:function(a,d){return new b.Dimension(b.operate(a,this.value,
  41
+d.value),this.unit||d.unit)}}})(q("less/tree"));(function(b){b.Directive=function(a,d){this.name=a;if(Array.isArray(d))this.ruleset=new b.Ruleset([],d);else this.value=d};b.Directive.prototype={toCSS:function(a,d){if(this.ruleset){this.ruleset.root=true;return this.name+(d.compress?"{":" {\n  ")+this.ruleset.toCSS(a,d).trim().replace(/\n/g,"\n  ")+(d.compress?"}":"\n}\n")}else return this.name+" "+this.value.toCSS()+";\n"},eval:function(a){a.frames.unshift(this);this.ruleset&&this.ruleset.evalRules(a);
  42
+a.frames.shift();return this},variable:function(a){return b.Ruleset.prototype.variable.call(this.ruleset,a)},find:function(){return b.Ruleset.prototype.find.apply(this.ruleset,arguments)},rulesets:function(){return b.Ruleset.prototype.rulesets.apply(this.ruleset)}}})(q("less/tree"));(function(b){b.Element=function(a,d){this.combinator=a instanceof b.Combinator?a:new b.Combinator(a);this.value=d.trim()};b.Element.prototype.toCSS=function(a){return this.combinator.toCSS(a||{})+this.value};b.Combinator=
  43
+function(a){this.value=a===" "?" ":a?a.trim():""};b.Combinator.prototype.toCSS=function(a){return{"":""," ":" ","&":"",":":" :","::":"::","+":a.compress?"+":" + ","~":a.compress?"~":" ~ ",">":a.compress?">":" > "}[this.value]}})(q("less/tree"));(function(b){b.Expression=function(a){this.value=a};b.Expression.prototype={eval:function(a){return this.value.length>1?new b.Expression(this.value.map(function(d){return d.eval(a)})):this.value[0].eval(a)},toCSS:function(){return this.value.map(function(a){return a.toCSS()}).join(" ")}}})(q("less/tree"));
  44
+(function(b){b.Import=function(a,d){var f=this;this._path=a;this.path=a instanceof b.Quoted?/\.(le?|c)ss$/.test(a.content)?a.content:a.content+".less":a.value.content||a.value;(this.css=/css$/.test(this.path))||d.push(this.path,function(e){if(!e)throw new Error("Error parsing "+f.path);f.root=e})};b.Import.prototype={toCSS:function(){return this.css?"@import "+this._path.toCSS()+";\n":""},eval:function(){if(this.css)return this;else{for(var a=0;a<this.root.rules.length;a++)this.root.rules[a]instanceof
  45
+b.Import&&Array.prototype.splice.apply(this.root.rules,[a,1].concat(this.root.rules[a].eval()));return this.root.rules}}}})(q("less/tree"));(function(b){b.Keyword=function(a){this.value=a};b.Keyword.prototype={eval:function(){return this},toCSS:function(){return this.value}}})(q("less/tree"));(function(b){b.mixin={};b.mixin.Call=function(a,d,f){this.selector=new b.Selector(a);this.arguments=d;this.index=f};b.mixin.Call.prototype={eval:function(a){for(var d,f=[],e=false,i=0;i<a.frames.length;i++)if((d=
  46
+a.frames[i].find(this.selector)).length>0){for(i=0;i<d.length;i++)if(d[i].match(this.arguments,a))try{Array.prototype.push.apply(f,d[i].eval(this.arguments,a).rules);e=true}catch(j){throw{message:j.message,index:j.index,call:this.index};}if(e)return f;else throw{message:"No matching definition was found for `"+this.selector.toCSS().trim()+"("+this.arguments.map(function(k){return k.toCSS()}).join(", ")+")`",index:this.index};}throw{message:this.selector.toCSS().trim()+" is undefined",index:this.index};
  47
+}};b.mixin.Definition=function(a,d,f){this.name=a;this.selectors=[new b.Selector([new b.Element(null,a)])];this.params=d;this.arity=d.length;this.rules=f;this._lookups={};this.required=d.reduce(function(e,i){return i.name&&!i.value?e+1:e},0);this.parent=b.Ruleset.prototype};b.mixin.Definition.prototype={toCSS:function(){return""},variable:function(a){return this.parent.variable.call(this,a)},find:function(){return this.parent.find.apply(this,arguments)},rulesets:function(){return this.parent.rulesets.apply(this)},
  48
+eval:function(a,d){for(var f=new b.Ruleset(null,[]),e=0,i;e<this.params.length;e++)if(this.params[e].name)if(i=a&&a[e]||this.params[e].value)f.rules.unshift(new b.Rule(this.params[e].name,i.eval(d)));else throw{message:"wrong number of arguments for "+this.name+" ("+a.length+" for "+this.arity+")"};return(new b.Ruleset(null,this.rules)).evalRules({frames:[this,f].concat(d.frames)})},match:function(a,d){var f=a&&a.length||0;if(f<this.required)return false;for(var e=0;e<Math.min(f,this.arity);e++)if(!this.params[e].name)if(!a[e].wildcard)if(a[e].eval(d).toCSS()!=
  49
+this.params[e].value.eval(d).toCSS())return false;return true}}})(q("less/tree"));(function(b){b.Operation=function(a,d){this.op=a.trim();this.operands=d};b.Operation.prototype.eval=function(a){var d=this.operands[0].eval(a);a=this.operands[1].eval(a);var f;if(d instanceof b.Dimension&&a instanceof b.Color)if(this.op==="*"||this.op==="+"){f=a;a=d;d=f}else throw{name:"OperationError",message:"Can't substract or divide a color from a number"};return d.operate(this.op,a)};b.operate=function(a,d,f){switch(a){case "+":return d+
  50
+f;case "-":return d-f;case "*":return d*f;case "/":return d/f}}})(q("less/tree"));(function(b){b.Quoted=function(a,d){this.value=a;this.content=d};b.Quoted.prototype={toCSS:function(){return this.value},eval:function(){return this}}})(q("less/tree"));(function(b){b.Rule=function(a,d,f){this.name=a;this.value=d instanceof b.Value?d:new b.Value([d]);this.index=f;this.variable=a.charAt(0)==="@"?true:false};b.Rule.prototype.toCSS=function(a){return this.variable?"":this.name+(a.compress?":":": ")+this.value.toCSS(a)+
  51
+";"};b.Rule.prototype.eval=function(a){return new b.Rule(this.name,this.value.eval(a))};b.Shorthand=function(a,d){this.a=a;this.b=d};b.Shorthand.prototype={toCSS:function(a){return this.a.toCSS(a)+"/"+this.b.toCSS(a)},eval:function(){return this}}})(q("less/tree"));(function(b){b.Ruleset=function(a,d){this.selectors=a;this.rules=d;this._lookups={}};b.Ruleset.prototype={eval:function(){return this},evalRules:function(a){var d=[];this.rules.forEach(function(f){if(f.evalRules)d.push(f.evalRules(a));
  52
+else f instanceof b.mixin.Call?Array.prototype.push.apply(d,f.eval(a)):d.push(f.eval?f.eval(a):"")});this.rules=d;return this},match:function(a){return!a||a.length===0},variable:function(a){return this._variables?this._variables[a]:(this._variables=this.rules.reduce(function(d,f){if(f instanceof b.Rule&&f.variable===true)d[f.name]=f;return d},{}))[a]},rulesets:function(){return this._rulesets?this._rulesets:(this._rulesets=this.rules.filter(function(a){if(a instanceof b.Ruleset||a instanceof b.mixin.Definition)return a}))},
  53
+find:function(a,d){d=d||this;var f=[],e=a.toCSS();if(e in this._lookups)return this._lookups[e];this.rulesets().forEach(function(i){if(i!==d)for(var j=0;j<i.selectors.length;j++)if(a.match(i.selectors[j])){a.elements.length>1?Array.prototype.push.apply(f,i.find(new b.Selector(a.elements.slice(1)),d)):f.push(i);break}});return this._lookups[e]=f},toCSS:function(a,d){var f=[],e=[],i=[],j=[];if(this.root)for(var k=0;k<this.rules.length;k++)this.rules[k]instanceof b.Import&&Array.prototype.splice.apply(this.rules,
  54
+[k,1].concat(this.rules[k].eval(d)));else if(a.length===0)j=this.selectors.map(function(r){return[r]});else for(k=0;k<this.selectors.length;k++)for(var o=0;o<a.length;o++)j.push(a[o].concat([this.selectors[k]]));d.frames.unshift(this);for(k=0;k<this.rules.length;k++)this.rules[k]instanceof b.mixin.Call&&Array.prototype.splice.apply(this.rules,[k,1].concat(this.rules[k].eval(d)));for(k=0;k<this.rules.length;k++){a=this.rules[k];if(a instanceof b.Directive)i.push(a.eval(d).toCSS(j,d));else if(a.rules)i.push(a.toCSS(j,
  55
+d));else if(a instanceof b.Comment)a.silent||(this.root?i.push(a.toCSS(d)):e.push(a.toCSS(d)));else if(a.toCSS&&!a.variable)e.push(a.eval(d).toCSS(d));else a.value&&!a.variable&&e.push(a.value.toString())}i=i.join("");if(this.root)f.push(e.join(d.compress?"":"\n"));else if(e.length>0){j=j.map(function(r){return r.map(function(t){return t.toCSS(d)}).join("").trim()}).join(d.compress?",":j.length>3?",\n":", ");f.push(j,(d.compress?"{":" {\n  ")+e.join(d.compress?"":"\n  ")+(d.compress?"}":"\n}\n"))}f.push(i);
  56
+d.frames.shift();return f.join("")+(d.compress?"\n":"")}}})(q("less/tree"));(function(b){b.Selector=function(a){this.elements=a;if(this.elements[0].combinator.value==="")this.elements[0].combinator.value=" "};b.Selector.prototype.match=function(a){return this.elements[0].value===a.elements[0].value?true:false};b.Selector.prototype.toCSS=function(a){if(this._css)return this._css;return this._css=this.elements.map(function(d){return typeof d==="string"?" "+d.trim():d.toCSS(a)}).join("")}})(q("less/tree"));
  57
+(function(b){b.URL=function(a){this.value=a};b.URL.prototype={toCSS:function(){return"url("+this.value.toCSS()+")"},eval:function(){return this}}})(q("less/tree"));(function(b){b.Value=function(a){this.value=a;this.is="value"};b.Value.prototype={eval:function(a){return this.value.length===1?this.value[0].eval(a):new b.Value(this.value.map(function(d){return d.eval(a)}))},toCSS:function(a){return this.value.map(function(d){return d.toCSS(a)}).join(a.compress?",":", ")}}})(q("less/tree"));(function(b){b.Variable=
  58
+function(a,d){this.name=a;this.index=d};b.Variable.prototype={eval:function(a){var d,f,e=this.name;if(d=b.find(a.frames,function(i){if(f=i.variable(e))return f.value.eval(a)}))return d;else throw{message:"variable "+this.name+" is undefined",index:this.index};}}})(q("less/tree"));q("less/tree").find=function(b,a){for(var d=0,f;d<b.length;d++)if(f=a.call(b,b[d]))return f;return null};var F=location.protocol==="file:";p.env=location.hostname=="127.0.0.1"||location.hostname=="0.0.0.0"||location.hostname==
  59
+"localhost"||location.port.length>0||F?"development":"production";p.async=false;p.poll=F?1E3:1500;p.watch=function(){return this.watchMode=true};p.unwatch=function(){return this.watchMode=false};if(p.env==="development"){p.optimization=0;/!watch/.test(location.hash)&&p.watch();p.watchTimer=setInterval(function(){p.watchMode&&K(function(b,a,d){b&&D(b.toCSS(),a,d.lastModified)})},p.poll)}else p.optimization=3;var y=typeof u.localStorage==="undefined"?null:u.localStorage,G=O('link[rel="stylesheet/less"]'),
  60
+T=endTime=new Date;p.refresh=function(b){K(function(a,d,f){if(f.local)x("loading "+d.href+" from cache.");else{x("parsed "+d.href+" successfully.");D(a.toCSS(),d,f.lastModified)}x("css for "+d.href+" generated in "+(new Date-endTime)+"ms");f.remaining===0&&x("css generated in "+(new Date-T)+"ms");endTime=new Date},b)};p.refresh()})(window);

0 notes on commit f3af1a2

Please sign in to comment.
Something went wrong with that request. Please try again.