PageRenderTime 53ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/Console/Command/Task/AssetBuildTask.php

https://github.com/renan/asset_compress
PHP | 291 lines | 176 code | 29 blank | 86 comment | 34 complexity | e0cfcaa470c81937400ca8d4c33e2939 MD5 | raw file
  1. <?php
  2. App::uses('Shell', 'Console');
  3. App::uses('Folder', 'Utility');
  4. App::uses('AssetConfig', 'AssetCompress.Lib');
  5. App::uses('AssetCompiler', 'AssetCompress.Lib');
  6. App::uses('AssetCache', 'AssetCompress.Lib');
  7. class AssetBuildTask extends Shell {
  8. protected $_Config;
  9. protected $_themes = array();
  10. protected $_files = array();
  11. protected $_tokens = array();
  12. /**
  13. * Array of tokens that indicate a helper call.
  14. *
  15. * @var array
  16. */
  17. public $helperTokens = array(
  18. '$assetCompress', 'AssetCompress'
  19. );
  20. /**
  21. * Array of helper methods to look for.
  22. *
  23. * @var array
  24. */
  25. protected $_methods = array('addCss', 'addScript');
  26. /**
  27. * Set the Configuration object that will be used.
  28. *
  29. * @return void
  30. */
  31. public function setConfig(AssetConfig $Config) {
  32. $this->_Config = $Config;
  33. $this->Compiler = new AssetCompiler($this->_Config);
  34. $this->Cacher = new AssetCache($this->_Config);
  35. }
  36. public function setThemes($themes) {
  37. $this->_themes = $themes;
  38. }
  39. /**
  40. * Build all the files declared in the Configuration object.
  41. *
  42. * @return void
  43. */
  44. public function buildIni() {
  45. $targets = $this->_Config->targets('js');
  46. foreach ($targets as $t) {
  47. $this->_buildTarget($t);
  48. }
  49. $targets = $this->_Config->targets('css');
  50. foreach ($targets as $t) {
  51. $this->_buildTarget($t);
  52. }
  53. }
  54. /**
  55. * Generate dynamically declared build targets in a set of paths.
  56. *
  57. * @param array $paths Array of paths to scan for dynamic builds
  58. * @return void
  59. */
  60. public function buildDynamic($paths) {
  61. $this->_collectFiles($paths);
  62. $this->scanFiles();
  63. $this->parse();
  64. $this->_buildFiles();
  65. }
  66. /**
  67. * Accessor for testing, sets files.
  68. *
  69. * @param array $files Array of files to scan
  70. * @return void
  71. */
  72. public function setFiles($files) {
  73. $this->_files = $files;
  74. }
  75. /**
  76. * Collects the files to scan and generate build files for.
  77. *
  78. * @param array $paths
  79. */
  80. protected function _collectFiles($paths) {
  81. foreach ($paths as $path) {
  82. $Folder = new Folder($path);
  83. $files = $Folder->findRecursive('.*\.(ctp|thtml|inc|tpl)', true);
  84. $this->_files = array_merge($this->_files, $files);
  85. }
  86. }
  87. /**
  88. * Scan each file for assetCompress helper calls. Only pull out the
  89. * calls to the helper.
  90. *
  91. * @return void
  92. */
  93. public function scanFiles() {
  94. $calls = array();
  95. foreach ($this->_files as $file) {
  96. $this->out('Scanning ' . $file . '...', 1, Shell::VERBOSE);
  97. $capturing = false;
  98. $content = file_get_contents($file);
  99. $tokens = token_get_all($content);
  100. foreach ($tokens as $i => $token) {
  101. // found a helper method start grabbing tokens.
  102. if (is_array($token) && in_array($token[1], $this->helperTokens) && is_array($tokens[$i + 2]) && in_array($tokens[$i + 2][1], $this->_methods)) {
  103. $capturing = true;
  104. $call = array();
  105. }
  106. if ($capturing) {
  107. $call[] = $token;
  108. }
  109. // end of function stop capturing
  110. if ($capturing && $token == ';') {
  111. $capturing = false;
  112. $calls[] = $call;
  113. }
  114. }
  115. }
  116. $this->_tokens = $calls;
  117. return $this->_tokens;
  118. }
  119. /**
  120. * Extract the file and build file names from the tokens.
  121. *
  122. * @return void
  123. */
  124. public function parse() {
  125. $fileMap = array();
  126. foreach ($this->_tokens as $call) {
  127. $method = $call[2][1];
  128. $args = array_slice($call, 3);
  129. list($files, $build) = $this->_parseArgs($args);
  130. if (!isset($fileMap[$method][$build])) {
  131. $fileMap[$method][$build] = array();
  132. }
  133. $fileMap[$method][$build] = array_merge($fileMap[$method][$build], $files);
  134. }
  135. $this->_buildFiles = $fileMap;
  136. return $this->_buildFiles;
  137. }
  138. /**
  139. * parses the arguments for a function call.
  140. *
  141. * @return array ($files, $buildFile)
  142. */
  143. protected function _parseArgs($tokens) {
  144. $files = array();
  145. $build = ':hash-default';
  146. $wasArray = false;
  147. while (true) {
  148. if (empty($tokens)) {
  149. break;
  150. }
  151. $token = array_shift($tokens);
  152. if ($token[0] == T_ARRAY) {
  153. $wasArray = true;
  154. $files = $this->_parseArray($tokens);
  155. }
  156. if ($token[0] == T_CONSTANT_ENCAPSED_STRING && $wasArray) {
  157. $build = trim($token[1], '"\'');
  158. } elseif ($token[0] == T_CONSTANT_ENCAPSED_STRING) {
  159. $files[] = trim($token[1], '"\'');
  160. }
  161. }
  162. if (!$wasArray && count($files) == 2) {
  163. $build = array_pop($files);
  164. }
  165. return array($files, $build);
  166. }
  167. /**
  168. * Parses an array argument
  169. *
  170. * @return array Array of array members
  171. */
  172. protected function _parseArray(&$tokens) {
  173. $files = array();
  174. while (true) {
  175. if (empty($tokens)) {
  176. break;
  177. }
  178. $token = array_shift($tokens);
  179. if ($token[0] == T_CONSTANT_ENCAPSED_STRING) {
  180. $files[] = trim($token[1], '"\'');
  181. }
  182. // end of array
  183. if ($token[0] == ')') {
  184. break;
  185. }
  186. }
  187. return $files;
  188. }
  189. /**
  190. * Generate the build files for css and scripts.
  191. *
  192. * @return void
  193. */
  194. protected function _buildFiles() {
  195. foreach ($this->_methods as $method) {
  196. if (empty($this->_buildFiles[$method])) {
  197. continue;
  198. }
  199. foreach ($this->_buildFiles[$method] as $target => $contents) {
  200. if (strpos($target, ':hash') === 0) {
  201. $target = md5(implode('_', $contents));
  202. }
  203. $ext = $method == 'addScript' ? '.js' : '.css';
  204. $target = $this->_addExt($target, $ext);
  205. $this->_Config->files($target, $contents);
  206. $this->_buildTarget($target);
  207. }
  208. }
  209. }
  210. /**
  211. * Generate and save the cached file for a build target.
  212. *
  213. * @param string $build The build to generate.
  214. * @return void
  215. */
  216. protected function _buildTarget($build) {
  217. if ($this->_Config->isThemed($build)) {
  218. foreach ($this->_themes as $theme) {
  219. $this->_Config->theme($theme);
  220. $this->_generateFile($build);
  221. }
  222. } else {
  223. $this->_generateFile($build);
  224. }
  225. }
  226. /**
  227. * Generate a build file.
  228. *
  229. * @param string $build The build name to generate.
  230. * @return void
  231. */
  232. protected function _generateFile($build) {
  233. $name = $this->Cacher->buildFileName($build);
  234. if ($this->Cacher->isFresh($build) && empty($this->params['force'])) {
  235. $this->out('<info>Skip building</info> ' . $name . ' existing file is still fresh.');
  236. return;
  237. }
  238. // Clear the timestamp so it can be regenerated.
  239. $this->Cacher->setTimestamp($build, 0);
  240. $name = $this->Cacher->buildFileName($build);
  241. try {
  242. $this->out('<success>Saving file</success> for ' . $name);
  243. $contents = $this->Compiler->generate($build);
  244. $this->Cacher->write($build, $contents);
  245. } catch (Exception $e) {
  246. $this->err('Error: ' . $e->getMessage());
  247. }
  248. }
  249. /**
  250. * Adds an extension if the file doesn't already end with it.
  251. *
  252. * @param string $file Filename
  253. * @param string $ext Extension with .
  254. * @return string
  255. */
  256. protected function _addExt($file, $ext) {
  257. if (substr($file, strlen($ext) * -1) !== $ext) {
  258. $file .= $ext;
  259. }
  260. return $file;
  261. }
  262. }