1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36:
37:
38: Yii::import('application.components.util.*');
39:
40: 41: 42: 43:
44: 45: 46: 47: 48: 49: 50:
51: class ThemeBuildCommand extends CConsoleCommand {
52:
53: 54: 55:
56: public $inputDir = '../';
57:
58: 59: 60:
61: public $outputFile = 'components/ThemeGenerator/templates/generatedRules.php';
62: public $moduleOverridesFile =
63: 'components/ThemeGenerator/templates/generatedModuleOverrides.php';
64:
65: 66: 67:
68: public function run($args) {
69: if (isset($args[0])) {
70: $this->inputDir = $args[0];
71: }
72:
73: if (isset($args[1])) {
74: $this->outputFile = $args[1];
75: }
76:
77: if (isset($args[0]) && $args[0] == '--keys') {
78: echo "These are the avaliable theming keys\n";
79:
80: foreach(ThemeGenerator::getProfileKeys() as $key) {
81: echo "$key\n";
82: }
83:
84: return;
85: }
86:
87: echo "Building theme...\n";
88:
89: $paths = $this->getCssFiles ($this->inputDir);
90:
91: $length = count($paths);
92:
93: if ($length < 1) {
94: echo "Error: no Css files found in directory: $this->inputDir";
95: return;
96: }
97:
98: echo "$length css files found\n";
99: echo "Scanning for theme tags\n";
100: $counter = 0.0;
101:
102:
103: $matches = array();
104: foreach ($paths as $i => $path) {
105: $matches = array_merge($matches, $this->scanCssFile($path));
106:
107:
108: while ($counter < $i/$length) {
109: $counter+= 0.1;
110: $this->progressBar($counter);
111: }
112:
113: }
114:
115: $matchesLength = count($matches);
116: echo "\r$matchesLength rules found \n";
117:
118: if ($matchesLength < 1) {
119: echo "No rules found, aborting\n";
120: return;
121: }
122:
123: echo "Formatting rules...\n";
124:
125:
126: $output = "<?php ".
127: "/* This file is generated by ThemeBuildCommand.php. Do not edit manually */\n".
128: "return \"\n";
129: foreach ($matches as $selector => $rule) {
130: $output .= $this->formatRule($selector, $rule);
131: }
132: $output .= "\n \"; ?>";
133:
134: $this->writeFile ($this->outputFile, $output);
135:
136:
137: $output = "<?php ".
138: "/* This file is generated by ThemeBuildCommand.php. Do not edit manually */\n".
139: "return \"\n";
140: foreach ($matches as $selector => $rule) {
141: $output .= $this->formatModuleOverridesRule($selector, $rule);
142: }
143: $output .= "\n \"; ?>";
144:
145: $this->writeFile ($this->moduleOverridesFile, $output);
146: }
147:
148: public function writeFile ($outputFile, $output) {
149:
150: if (file_exists($outputFile) && sha1_file($outputFile) == sha1($output)) {
151: echo "No changes detected in $outputFile, Aborting\n";
152: return;
153: }
154:
155: echo "Saving to $outputFile\n";
156: file_put_contents ($outputFile, $output);
157: }
158:
159: 160: 161: 162: 163:
164: public function getCssFiles($root) {
165: $iter = new RecursiveIteratorIterator(
166: new RecursiveDirectoryIterator($root, RecursiveDirectoryIterator::SKIP_DOTS),
167: RecursiveIteratorIterator::SELF_FIRST,
168: RecursiveIteratorIterator::CATCH_GET_CHILD
169: );
170:
171: $paths = array($root);
172: foreach ($iter as $path => $dir) {
173: if (preg_match('/\.css$/', $path)) {
174: $paths[] = $path;
175: }
176: }
177:
178: return $paths;
179: }
180:
181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193:
194: public function scanCssFile($path) {
195: $handle = fopen($path, "r");
196: $rules = array();
197:
198: $lineNumber = -1;
199: while (($line = fgets($handle)) !== false) {
200: $lineNumber++;
201: if (!preg_match('/@theme/', $line)) continue;
202:
203: list($selector, $comment, $rule) = $this->makeRule($path, $lineNumber);
204:
205:
206: if (!isset($rules[$selector])) {
207: $rules[$selector] = array($rule);
208: $rules[$selector]['comments'] = array($comment);
209: continue;
210: }
211:
212:
213: if (in_array($rule, $rules[$selector])) continue;
214: $rules[$selector][] = $rule;
215:
216:
217: if (in_array($comment, $rules[$selector]['comments'])) continue;
218: $rules[$selector]['comments'][] = $comment;
219: }
220:
221: fclose($handle);
222:
223: return $rules;
224: }
225:
226: 227: 228: 229: 230:
231: public function makeRule($file, $lineNumber) {
232: $lines = file($file);
233: $themeLine = $lines[$lineNumber];
234:
235: $stripped = preg_replace('/.*@theme\ *(.*)\*\//', '\1', $themeLine);
236:
237:
238: $stripped = preg_replace('/\ \ */', ' ', $stripped);
239: $stripped = preg_replace('/:/', '', $stripped);
240: $params = explode(' ', $stripped);
241:
242:
243: while(!preg_match('/line\ [0-9]+/', $lines[$lineNumber])) {
244: $lineNumber--;
245:
246: if ($lineNumber < 0) {
247: throw new Exception("Backtracked and found no comment in $file", 1);
248: }
249: }
250:
251: $comment = $lines[$lineNumber];
252: $selector = '';
253:
254:
255: while(!preg_match('/{/', $lines[$lineNumber])) {
256: $lineNumber++;
257: $selector .= $lines[$lineNumber];
258: }
259:
260:
261: $selector = preg_replace('/\n[^$]/', "\n ", $selector);
262:
263: $rule = $params[0];
264: $value = $params[1];
265:
266:
267: if (!in_array($value, ThemeGenerator::getProfileKeys())) {
268: $comment = preg_replace('/\/\*(.*)\*\//', '\1', $comment);
269: throw new Exception("\nTheme Key '$value' is not a valid key.\nFound at$comment");
270: }
271:
272: return array (
273: $selector,
274: $comment,
275: array (
276: 'rule' => $rule,
277: 'value' => $value
278: )
279: );
280:
281: }
282:
283: 284: 285: 286: 287:
288: public function formatRule($selector, $rule, $addNoThemeRule=false) {
289:
290: $comments = $rule['comments'];
291: unset($rule['comments']);
292: $string = "\n";
293:
294: foreach($comments as $index => $comment) {
295: $string .= " $comment";
296: }
297:
298: $string .= " $selector";
299:
300:
301:
302: if ($addNoThemeRule)
303: $string = preg_replace('/\ *{/', ':not(.no-theme) {', $string);
304:
305: foreach($rule as $value) {
306: if (preg_match ('/_override$/', $value['value'])) {
307: $string .= " ".
308: '".((isset ($colors[\''.$value['value'].'\']) && '.
309: '$colors[\''.$value['value'].'\']) ? '.
310: '"'. $value['rule'].': $colors['.$value['value'].']" : "")."'."\n";
311: } else {
312: $string .= " ".$value['rule'].': $colors['.$value['value']."]\n";
313: }
314: }
315: $string .= " }\n";
316:
317: return $string;
318: }
319:
320: 321: 322: 323:
324: public function formatModuleOverridesRule($selector, $rule) {
325: static $rulesCache = array ();
326:
327:
328:
329: $selector = preg_replace ('/(page-title\.)[a-zA-Z0-9]+/', '$1{\$module}', $selector);
330: $selector = preg_replace ('/(widget-title-bar\.)[a-zA-Z0-9]+/', '$1{\$module}', $selector);
331:
332: if (!isset ($rulesCache[$selector])) $rulesCache[$selector] = array ();
333:
334:
335: $comments = $rule['comments'];
336: unset($rule['comments']);
337: $string = "\n";
338:
339: foreach($comments as $index => $comment) {
340: $string .= " $comment";
341: }
342:
343: $string .= " $selector";
344:
345: $foundRule = false;
346: foreach($rule as $value) {
347: if (preg_match ('/_override$/', $value['value'])) {
348: $templated = preg_replace (
349: '/^([a-zA-Z0-9]+_)+[^_]+(_override)$/', '$1{\$module}$2', $value['value']);
350: if (!isset ($rulesCache[$selector][$templated])) {
351: $foundRule = true;
352:
353: $string .= " ".
354: '".((isset ($colors["'.$templated.'"]) && '.
355: '$colors["'.$templated.'"]) ? '.
356: '"'. $value['rule'].': {$colors["'.$templated.'"]}" : "")."'."\n";
357: $rulesCache[$selector][$templated] = true;
358: }
359: }
360: }
361: $string .= " }\n";
362:
363: if (!$foundRule) $string = '';
364: return $string;
365: }
366:
367: public function getHelp() {
368: return "\nBuilds a php theme file from various @theme tags in all CSS files. \nUsage: themebuild [INPUT DIRECTORY] [OUTPUT FILE]\nOptions: themebuild --keys \n This will list all the avaliable keys for theming.\n";
369: }
370:
371:
372: public function progressBar($amount) {
373: echo "\r".($amount*100)."% |";
374: for ($j = 0; $j < 10; $j++) {
375: if($j < $amount * 10) {
376: echo '-';
377: } else {
378: echo ' ';
379: }
380: }
381: echo '|';
382: }
383:
384: }
385:
386: ?>
387: