~tsep-dev/tsep/0.9-beta

« back to all changes in this revision

Viewing changes to branches/symfony/cake/console/libs/tasks/extract.php

  • Committer: geoffreyfishing
  • Date: 2011-01-11 23:46:12 UTC
  • Revision ID: svn-v4:ae0de26e-ed09-4cbe-9a20-e40b4c60ac6c::125
Created a symfony branch for future migration to symfony

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
}