Mercurial > hg > Members > shoshi > webvirt
comparison cake/console/libs/tasks/extract.php @ 0:261e66bd5a0c
hg init
author | Shoshi TAMAKI <shoshi@cr.ie.u-ryukyu.ac.jp> |
---|---|
date | Sun, 24 Jul 2011 21:08:31 +0900 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:261e66bd5a0c |
---|---|
1 <?php | |
2 /** | |
3 * Language string extractor | |
4 * | |
5 * PHP versions 4 and 5 | |
6 * | |
7 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) | |
8 * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) | |
9 * | |
10 * Licensed under The MIT License | |
11 * Redistributions of files must retain the above copyright notice. | |
12 * | |
13 * @copyright Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org) | |
14 * @link http://cakephp.org CakePHP(tm) Project | |
15 * @package cake | |
16 * @subpackage cake.cake.console.libs | |
17 * @since CakePHP(tm) v 1.2.0.5012 | |
18 * @license MIT License (http://www.opensource.org/licenses/mit-license.php) | |
19 */ | |
20 | |
21 /** | |
22 * Language string extractor | |
23 * | |
24 * @package cake | |
25 * @subpackage cake.cake.console.libs.tasks | |
26 */ | |
27 class ExtractTask extends Shell { | |
28 | |
29 /** | |
30 * Paths to use when looking for strings | |
31 * | |
32 * @var string | |
33 * @access private | |
34 */ | |
35 var $__paths = array(); | |
36 | |
37 /** | |
38 * Files from where to extract | |
39 * | |
40 * @var array | |
41 * @access private | |
42 */ | |
43 var $__files = array(); | |
44 | |
45 /** | |
46 * Merge all domains string into the default.pot file | |
47 * | |
48 * @var boolean | |
49 * @access private | |
50 */ | |
51 var $__merge = false; | |
52 | |
53 /** | |
54 * Current file being processed | |
55 * | |
56 * @var string | |
57 * @access private | |
58 */ | |
59 var $__file = null; | |
60 | |
61 /** | |
62 * Contains all content waiting to be write | |
63 * | |
64 * @var string | |
65 * @access private | |
66 */ | |
67 var $__storage = array(); | |
68 | |
69 /** | |
70 * Extracted tokens | |
71 * | |
72 * @var array | |
73 * @access private | |
74 */ | |
75 var $__tokens = array(); | |
76 | |
77 /** | |
78 * Extracted strings | |
79 * | |
80 * @var array | |
81 * @access private | |
82 */ | |
83 var $__strings = array(); | |
84 | |
85 /** | |
86 * Destination path | |
87 * | |
88 * @var string | |
89 * @access private | |
90 */ | |
91 var $__output = null; | |
92 | |
93 /** | |
94 * Execution method always used for tasks | |
95 * | |
96 * @return void | |
97 * @access private | |
98 */ | |
99 function execute() { | |
100 if (isset($this->params['files']) && !is_array($this->params['files'])) { | |
101 $this->__files = explode(',', $this->params['files']); | |
102 } | |
103 if (isset($this->params['paths'])) { | |
104 $this->__paths = explode(',', $this->params['paths']); | |
105 } else { | |
106 $defaultPath = $this->params['working']; | |
107 $message = sprintf(__("What is the full path you would like to extract?\nExample: %s\n[Q]uit [D]one", true), $this->params['root'] . DS . 'myapp'); | |
108 while (true) { | |
109 $response = $this->in($message, null, $defaultPath); | |
110 if (strtoupper($response) === 'Q') { | |
111 $this->out(__('Extract Aborted', true)); | |
112 $this->_stop(); | |
113 } elseif (strtoupper($response) === 'D') { | |
114 $this->out(); | |
115 break; | |
116 } elseif (is_dir($response)) { | |
117 $this->__paths[] = $response; | |
118 $defaultPath = 'D'; | |
119 } else { | |
120 $this->err(__('The directory path you supplied was not found. Please try again.', true)); | |
121 } | |
122 $this->out(); | |
123 } | |
124 } | |
125 | |
126 if (isset($this->params['output'])) { | |
127 $this->__output = $this->params['output']; | |
128 } else { | |
129 $message = sprintf(__("What is the full path you would like to output?\nExample: %s\n[Q]uit", true), $this->__paths[0] . DS . 'locale'); | |
130 while (true) { | |
131 $response = $this->in($message, null, $this->__paths[0] . DS . 'locale'); | |
132 if (strtoupper($response) === 'Q') { | |
133 $this->out(__('Extract Aborted', true)); | |
134 $this->_stop(); | |
135 } elseif (is_dir($response)) { | |
136 $this->__output = $response . DS; | |
137 break; | |
138 } else { | |
139 $this->err(__('The directory path you supplied was not found. Please try again.', true)); | |
140 } | |
141 $this->out(); | |
142 } | |
143 } | |
144 | |
145 if (isset($this->params['merge'])) { | |
146 $this->__merge = !(strtolower($this->params['merge']) === 'no'); | |
147 } else { | |
148 $this->out(); | |
149 $response = $this->in(sprintf(__('Would you like to merge all domains strings into the default.pot file?', true)), array('y', 'n'), 'n'); | |
150 $this->__merge = strtolower($response) === 'y'; | |
151 } | |
152 | |
153 if (empty($this->__files)) { | |
154 $this->__searchFiles(); | |
155 } | |
156 $this->__extract(); | |
157 } | |
158 | |
159 /** | |
160 * Extract text | |
161 * | |
162 * @return void | |
163 * @access private | |
164 */ | |
165 function __extract() { | |
166 $this->out(); | |
167 $this->out(); | |
168 $this->out(__('Extracting...', true)); | |
169 $this->hr(); | |
170 $this->out(__('Paths:', true)); | |
171 foreach ($this->__paths as $path) { | |
172 $this->out(' ' . $path); | |
173 } | |
174 $this->out(__('Output Directory: ', true) . $this->__output); | |
175 $this->hr(); | |
176 $this->__extractTokens(); | |
177 $this->__buildFiles(); | |
178 $this->__writeFiles(); | |
179 $this->__paths = $this->__files = $this->__storage = array(); | |
180 $this->__strings = $this->__tokens = array(); | |
181 $this->out(); | |
182 $this->out(__('Done.', true)); | |
183 } | |
184 | |
185 /** | |
186 * Show help options | |
187 * | |
188 * @return void | |
189 * @access public | |
190 */ | |
191 function help() { | |
192 $this->out(__('CakePHP Language String Extraction:', true)); | |
193 $this->hr(); | |
194 $this->out(__('The Extract script generates .pot file(s) with translations', true)); | |
195 $this->out(__('By default the .pot file(s) will be place in the locale directory of -app', true)); | |
196 $this->out(__('By default -app is ROOT/app', true)); | |
197 $this->hr(); | |
198 $this->out(__('Usage: cake i18n extract <command> <param1> <param2>...', true)); | |
199 $this->out(); | |
200 $this->out(__('Params:', true)); | |
201 $this->out(__(' -app [path...]: directory where your application is located', true)); | |
202 $this->out(__(' -root [path...]: path to install', true)); | |
203 $this->out(__(' -core [path...]: path to cake directory', true)); | |
204 $this->out(__(' -paths [comma separated list of paths, full path is needed]', true)); | |
205 $this->out(__(' -merge [yes|no]: Merge all domains strings into the default.pot file', true)); | |
206 $this->out(__(' -output [path...]: Full path to output directory', true)); | |
207 $this->out(__(' -files: [comma separated list of files, full path to file is needed]', true)); | |
208 $this->out(); | |
209 $this->out(__('Commands:', true)); | |
210 $this->out(__(' cake i18n extract help: Shows this help message.', true)); | |
211 $this->out(); | |
212 } | |
213 | |
214 /** | |
215 * Extract tokens out of all files to be processed | |
216 * | |
217 * @return void | |
218 * @access private | |
219 */ | |
220 function __extractTokens() { | |
221 foreach ($this->__files as $file) { | |
222 $this->__file = $file; | |
223 $this->out(sprintf(__('Processing %s...', true), $file)); | |
224 | |
225 $code = file_get_contents($file); | |
226 $allTokens = token_get_all($code); | |
227 $this->__tokens = array(); | |
228 $lineNumber = 1; | |
229 | |
230 foreach ($allTokens as $token) { | |
231 if ((!is_array($token)) || (($token[0] != T_WHITESPACE) && ($token[0] != T_INLINE_HTML))) { | |
232 if (is_array($token)) { | |
233 $token[] = $lineNumber; | |
234 } | |
235 $this->__tokens[] = $token; | |
236 } | |
237 | |
238 if (is_array($token)) { | |
239 $lineNumber += count(explode("\n", $token[1])) - 1; | |
240 } else { | |
241 $lineNumber += count(explode("\n", $token)) - 1; | |
242 } | |
243 } | |
244 unset($allTokens); | |
245 $this->__parse('__', array('singular')); | |
246 $this->__parse('__n', array('singular', 'plural')); | |
247 $this->__parse('__d', array('domain', 'singular')); | |
248 $this->__parse('__c', array('singular')); | |
249 $this->__parse('__dc', array('domain', 'singular')); | |
250 $this->__parse('__dn', array('domain', 'singular', 'plural')); | |
251 $this->__parse('__dcn', array('domain', 'singular', 'plural')); | |
252 } | |
253 } | |
254 | |
255 /** | |
256 * Parse tokens | |
257 * | |
258 * @param string $functionName Function name that indicates translatable string (e.g: '__') | |
259 * @param array $map Array containing what variables it will find (e.g: domain, singular, plural) | |
260 * @return void | |
261 * @access private | |
262 */ | |
263 function __parse($functionName, $map) { | |
264 $count = 0; | |
265 $tokenCount = count($this->__tokens); | |
266 | |
267 while (($tokenCount - $count) > 1) { | |
268 list($countToken, $firstParenthesis) = array($this->__tokens[$count], $this->__tokens[$count + 1]); | |
269 if (!is_array($countToken)) { | |
270 $count++; | |
271 continue; | |
272 } | |
273 | |
274 list($type, $string, $line) = $countToken; | |
275 if (($type == T_STRING) && ($string == $functionName) && ($firstParenthesis == '(')) { | |
276 $position = $count; | |
277 $depth = 0; | |
278 | |
279 while ($depth == 0) { | |
280 if ($this->__tokens[$position] == '(') { | |
281 $depth++; | |
282 } elseif ($this->__tokens[$position] == ')') { | |
283 $depth--; | |
284 } | |
285 $position++; | |
286 } | |
287 | |
288 $mapCount = count($map); | |
289 $strings = array(); | |
290 while (count($strings) < $mapCount && ($this->__tokens[$position] == ',' || $this->__tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING)) { | |
291 if ($this->__tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING) { | |
292 $strings[] = $this->__tokens[$position][1]; | |
293 } | |
294 $position++; | |
295 } | |
296 | |
297 if ($mapCount == count($strings)) { | |
298 extract(array_combine($map, $strings)); | |
299 if (!isset($domain)) { | |
300 $domain = '\'default\''; | |
301 } | |
302 $string = $this->__formatString($singular); | |
303 if (isset($plural)) { | |
304 $string .= "\0" . $this->__formatString($plural); | |
305 } | |
306 $this->__strings[$this->__formatString($domain)][$string][$this->__file][] = $line; | |
307 } else { | |
308 $this->__markerError($this->__file, $line, $functionName, $count); | |
309 } | |
310 } | |
311 $count++; | |
312 } | |
313 } | |
314 | |
315 /** | |
316 * Build the translate template file contents out of obtained strings | |
317 * | |
318 * @return void | |
319 * @access private | |
320 */ | |
321 function __buildFiles() { | |
322 foreach ($this->__strings as $domain => $strings) { | |
323 foreach ($strings as $string => $files) { | |
324 $occurrences = array(); | |
325 foreach ($files as $file => $lines) { | |
326 $occurrences[] = $file . ':' . implode(';', $lines); | |
327 } | |
328 $occurrences = implode("\n#: ", $occurrences); | |
329 $header = '#: ' . str_replace($this->__paths, '', $occurrences) . "\n"; | |
330 | |
331 if (strpos($string, "\0") === false) { | |
332 $sentence = "msgid \"{$string}\"\n"; | |
333 $sentence .= "msgstr \"\"\n\n"; | |
334 } else { | |
335 list($singular, $plural) = explode("\0", $string); | |
336 $sentence = "msgid \"{$singular}\"\n"; | |
337 $sentence .= "msgid_plural \"{$plural}\"\n"; | |
338 $sentence .= "msgstr[0] \"\"\n"; | |
339 $sentence .= "msgstr[1] \"\"\n\n"; | |
340 } | |
341 | |
342 $this->__store($domain, $header, $sentence); | |
343 if ($domain != 'default' && $this->__merge) { | |
344 $this->__store('default', $header, $sentence); | |
345 } | |
346 } | |
347 } | |
348 } | |
349 | |
350 /** | |
351 * Prepare a file to be stored | |
352 * | |
353 * @return void | |
354 * @access private | |
355 */ | |
356 function __store($domain, $header, $sentence) { | |
357 if (!isset($this->__storage[$domain])) { | |
358 $this->__storage[$domain] = array(); | |
359 } | |
360 if (!isset($this->__storage[$domain][$sentence])) { | |
361 $this->__storage[$domain][$sentence] = $header; | |
362 } else { | |
363 $this->__storage[$domain][$sentence] .= $header; | |
364 } | |
365 } | |
366 | |
367 /** | |
368 * Write the files that need to be stored | |
369 * | |
370 * @return void | |
371 * @access private | |
372 */ | |
373 function __writeFiles() { | |
374 $overwriteAll = false; | |
375 foreach ($this->__storage as $domain => $sentences) { | |
376 $output = $this->__writeHeader(); | |
377 foreach ($sentences as $sentence => $header) { | |
378 $output .= $header . $sentence; | |
379 } | |
380 | |
381 $filename = $domain . '.pot'; | |
382 $File = new File($this->__output . $filename); | |
383 $response = ''; | |
384 while ($overwriteAll === false && $File->exists() && strtoupper($response) !== 'Y') { | |
385 $this->out(); | |
386 $response = $this->in(sprintf(__('Error: %s already exists in this location. Overwrite? [Y]es, [N]o, [A]ll', true), $filename), array('y', 'n', 'a'), 'y'); | |
387 if (strtoupper($response) === 'N') { | |
388 $response = ''; | |
389 while ($response == '') { | |
390 $response = $this->in(sprintf(__("What would you like to name this file?\nExample: %s", true), 'new_' . $filename), null, 'new_' . $filename); | |
391 $File = new File($this->__output . $response); | |
392 $filename = $response; | |
393 } | |
394 } elseif (strtoupper($response) === 'A') { | |
395 $overwriteAll = true; | |
396 } | |
397 } | |
398 $File->write($output); | |
399 $File->close(); | |
400 } | |
401 } | |
402 | |
403 /** | |
404 * Build the translation template header | |
405 * | |
406 * @return string Translation template header | |
407 * @access private | |
408 */ | |
409 function __writeHeader() { | |
410 $output = "# LANGUAGE translation of CakePHP Application\n"; | |
411 $output .= "# Copyright YEAR NAME <EMAIL@ADDRESS>\n"; | |
412 $output .= "#\n"; | |
413 $output .= "#, fuzzy\n"; | |
414 $output .= "msgid \"\"\n"; | |
415 $output .= "msgstr \"\"\n"; | |
416 $output .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n"; | |
417 $output .= "\"POT-Creation-Date: " . date("Y-m-d H:iO") . "\\n\"\n"; | |
418 $output .= "\"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\\n\"\n"; | |
419 $output .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n"; | |
420 $output .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n"; | |
421 $output .= "\"MIME-Version: 1.0\\n\"\n"; | |
422 $output .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n"; | |
423 $output .= "\"Content-Transfer-Encoding: 8bit\\n\"\n"; | |
424 $output .= "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n\n"; | |
425 return $output; | |
426 } | |
427 | |
428 /** | |
429 * Format a string to be added as a translateable string | |
430 * | |
431 * @param string $string String to format | |
432 * @return string Formatted string | |
433 * @access private | |
434 */ | |
435 function __formatString($string) { | |
436 $quote = substr($string, 0, 1); | |
437 $string = substr($string, 1, -1); | |
438 if ($quote == '"') { | |
439 $string = stripcslashes($string); | |
440 } else { | |
441 $string = strtr($string, array("\\'" => "'", "\\\\" => "\\")); | |
442 } | |
443 $string = str_replace("\r\n", "\n", $string); | |
444 return addcslashes($string, "\0..\37\\\""); | |
445 } | |
446 | |
447 /** | |
448 * Indicate an invalid marker on a processed file | |
449 * | |
450 * @param string $file File where invalid marker resides | |
451 * @param integer $line Line number | |
452 * @param string $marker Marker found | |
453 * @param integer $count Count | |
454 * @return void | |
455 * @access private | |
456 */ | |
457 function __markerError($file, $line, $marker, $count) { | |
458 $this->out(sprintf(__("Invalid marker content in %s:%s\n* %s(", true), $file, $line, $marker), true); | |
459 $count += 2; | |
460 $tokenCount = count($this->__tokens); | |
461 $parenthesis = 1; | |
462 | |
463 while ((($tokenCount - $count) > 0) && $parenthesis) { | |
464 if (is_array($this->__tokens[$count])) { | |
465 $this->out($this->__tokens[$count][1], false); | |
466 } else { | |
467 $this->out($this->__tokens[$count], false); | |
468 if ($this->__tokens[$count] == '(') { | |
469 $parenthesis++; | |
470 } | |
471 | |
472 if ($this->__tokens[$count] == ')') { | |
473 $parenthesis--; | |
474 } | |
475 } | |
476 $count++; | |
477 } | |
478 $this->out("\n", true); | |
479 } | |
480 | |
481 /** | |
482 * Search files that may contain translateable strings | |
483 * | |
484 * @return void | |
485 * @access private | |
486 */ | |
487 function __searchFiles() { | |
488 foreach ($this->__paths as $path) { | |
489 $Folder = new Folder($path); | |
490 $files = $Folder->findRecursive('.*\.(php|ctp|thtml|inc|tpl)', true); | |
491 $this->__files = array_merge($this->__files, $files); | |
492 } | |
493 } | |
494 } |