lessc.inc.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. <?php
  2. /**
  3. * This file provides the part of lessphp API (https://github.com/leafo/lessphp)
  4. * to be a drop-in replacement for following products:
  5. * - Drupal 7, by the less module v3.0+ (https://drupal.org/project/less)
  6. * - Symfony 2
  7. */
  8. // Register autoloader for non-composer installations
  9. if ( !class_exists( 'Less_Parser' ) ) {
  10. require_once __DIR__ . '/lib/Less/Autoloader.php';
  11. Less_Autoloader::register();
  12. }
  13. class lessc {
  14. static public $VERSION = Less_Version::less_version;
  15. public $importDir = '';
  16. protected $allParsedFiles = array();
  17. protected $libFunctions = array();
  18. protected $registeredVars = array();
  19. private $formatterName;
  20. private $options = array();
  21. public function __construct( $lessc = null, $sourceName = null ) {
  22. }
  23. public function setImportDir( $dirs ) {
  24. $this->importDir = (array)$dirs;
  25. }
  26. public function addImportDir( $dir ) {
  27. $this->importDir = (array)$this->importDir;
  28. $this->importDir[] = $dir;
  29. }
  30. public function setFormatter( $name ) {
  31. $this->formatterName = $name;
  32. }
  33. public function setPreserveComments( $preserve ) {
  34. }
  35. public function registerFunction( $name, $func ) {
  36. $this->libFunctions[$name] = $func;
  37. }
  38. public function unregisterFunction( $name ) {
  39. unset( $this->libFunctions[$name] );
  40. }
  41. public function setVariables( $variables ) {
  42. foreach ( $variables as $name => $value ) {
  43. $this->setVariable( $name, $value );
  44. }
  45. }
  46. public function setVariable( $name, $value ) {
  47. $this->registeredVars[$name] = $value;
  48. }
  49. public function unsetVariable( $name ) {
  50. unset( $this->registeredVars[$name] );
  51. }
  52. public function setOptions( $options ) {
  53. foreach ( $options as $name => $value ) {
  54. $this->setOption( $name, $value );
  55. }
  56. }
  57. public function setOption( $name, $value ) {
  58. $this->options[$name] = $value;
  59. }
  60. public function parse( $buffer, $presets = array() ) {
  61. $this->setVariables( $presets );
  62. $parser = new Less_Parser( $this->getOptions() );
  63. $parser->setImportDirs( $this->getImportDirs() );
  64. foreach ( $this->libFunctions as $name => $func ) {
  65. $parser->registerFunction( $name, $func );
  66. }
  67. $parser->parse( $buffer );
  68. if ( count( $this->registeredVars ) ) {
  69. $parser->ModifyVars( $this->registeredVars );
  70. }
  71. return $parser->getCss();
  72. }
  73. protected function getOptions() {
  74. $options = array( 'relativeUrls' => false );
  75. switch ( $this->formatterName ) {
  76. case 'compressed':
  77. $options['compress'] = true;
  78. break;
  79. }
  80. if ( is_array( $this->options ) ) {
  81. $options = array_merge( $options, $this->options );
  82. }
  83. return $options;
  84. }
  85. protected function getImportDirs() {
  86. $dirs_ = (array)$this->importDir;
  87. $dirs = array();
  88. foreach ( $dirs_ as $dir ) {
  89. $dirs[$dir] = '';
  90. }
  91. return $dirs;
  92. }
  93. public function compile( $string, $name = null ) {
  94. $oldImport = $this->importDir;
  95. $this->importDir = (array)$this->importDir;
  96. $this->allParsedFiles = array();
  97. $parser = new Less_Parser( $this->getOptions() );
  98. $parser->SetImportDirs( $this->getImportDirs() );
  99. if ( count( $this->registeredVars ) ) {
  100. $parser->ModifyVars( $this->registeredVars );
  101. }
  102. foreach ( $this->libFunctions as $name => $func ) {
  103. $parser->registerFunction( $name, $func );
  104. }
  105. $parser->parse( $string );
  106. $out = $parser->getCss();
  107. $parsed = Less_Parser::AllParsedFiles();
  108. foreach ( $parsed as $file ) {
  109. $this->addParsedFile( $file );
  110. }
  111. $this->importDir = $oldImport;
  112. return $out;
  113. }
  114. public function compileFile( $fname, $outFname = null ) {
  115. if ( !is_readable( $fname ) ) {
  116. throw new Exception( 'load error: failed to find '.$fname );
  117. }
  118. $pi = pathinfo( $fname );
  119. $oldImport = $this->importDir;
  120. $this->importDir = (array)$this->importDir;
  121. $this->importDir[] = Less_Parser::AbsPath( $pi['dirname'] ).'/';
  122. $this->allParsedFiles = array();
  123. $this->addParsedFile( $fname );
  124. $parser = new Less_Parser( $this->getOptions() );
  125. $parser->SetImportDirs( $this->getImportDirs() );
  126. if ( count( $this->registeredVars ) ) {
  127. $parser->ModifyVars( $this->registeredVars );
  128. }
  129. foreach ( $this->libFunctions as $name => $func ) {
  130. $parser->registerFunction( $name, $func );
  131. }
  132. $parser->parseFile( $fname );
  133. $out = $parser->getCss();
  134. $parsed = Less_Parser::AllParsedFiles();
  135. foreach ( $parsed as $file ) {
  136. $this->addParsedFile( $file );
  137. }
  138. $this->importDir = $oldImport;
  139. if ( $outFname !== null ) {
  140. return file_put_contents( $outFname, $out );
  141. }
  142. return $out;
  143. }
  144. public function checkedCompile( $in, $out ) {
  145. if ( !is_file( $out ) || filemtime( $in ) > filemtime( $out ) ) {
  146. $this->compileFile( $in, $out );
  147. return true;
  148. }
  149. return false;
  150. }
  151. /**
  152. * Execute lessphp on a .less file or a lessphp cache structure
  153. *
  154. * The lessphp cache structure contains information about a specific
  155. * less file having been parsed. It can be used as a hint for future
  156. * calls to determine whether or not a rebuild is required.
  157. *
  158. * The cache structure contains two important keys that may be used
  159. * externally:
  160. *
  161. * compiled: The final compiled CSS
  162. * updated: The time (in seconds) the CSS was last compiled
  163. *
  164. * The cache structure is a plain-ol' PHP associative array and can
  165. * be serialized and unserialized without a hitch.
  166. *
  167. * @param mixed $in Input
  168. * @param bool $force Force rebuild?
  169. * @return array lessphp cache structure
  170. */
  171. public function cachedCompile( $in, $force = false ) {
  172. // assume no root
  173. $root = null;
  174. if ( is_string( $in ) ) {
  175. $root = $in;
  176. } elseif ( is_array( $in ) and isset( $in['root'] ) ) {
  177. if ( $force or !isset( $in['files'] ) ) {
  178. // If we are forcing a recompile or if for some reason the
  179. // structure does not contain any file information we should
  180. // specify the root to trigger a rebuild.
  181. $root = $in['root'];
  182. } elseif ( isset( $in['files'] ) and is_array( $in['files'] ) ) {
  183. foreach ( $in['files'] as $fname => $ftime ) {
  184. if ( !file_exists( $fname ) or filemtime( $fname ) > $ftime ) {
  185. // One of the files we knew about previously has changed
  186. // so we should look at our incoming root again.
  187. $root = $in['root'];
  188. break;
  189. }
  190. }
  191. }
  192. } else {
  193. // TODO: Throw an exception? We got neither a string nor something
  194. // that looks like a compatible lessphp cache structure.
  195. return null;
  196. }
  197. if ( $root !== null ) {
  198. // If we have a root value which means we should rebuild.
  199. $out = array();
  200. $out['root'] = $root;
  201. $out['compiled'] = $this->compileFile( $root );
  202. $out['files'] = $this->allParsedFiles();
  203. $out['updated'] = time();
  204. return $out;
  205. } else {
  206. // No changes, pass back the structure
  207. // we were given initially.
  208. return $in;
  209. }
  210. }
  211. public function ccompile( $in, $out, $less = null ) {
  212. if ( $less === null ) {
  213. $less = new self;
  214. }
  215. return $less->checkedCompile( $in, $out );
  216. }
  217. public static function cexecute( $in, $force = false, $less = null ) {
  218. if ( $less === null ) {
  219. $less = new self;
  220. }
  221. return $less->cachedCompile( $in, $force );
  222. }
  223. public function allParsedFiles() {
  224. return $this->allParsedFiles;
  225. }
  226. protected function addParsedFile( $file ) {
  227. $this->allParsedFiles[Less_Parser::AbsPath( $file )] = filemtime( $file );
  228. }
  229. }