Changeset 376
- Timestamp:
- 06/12/08 18:26:52 (13 months ago)
- Location:
- trunk
- Files:
-
- 1 added
- 4 modified
-
babel/messages/extract.py (modified) (1 diff)
-
babel/messages/jslexer.py (added)
-
babel/messages/tests/extract.py (modified) (2 diffs)
-
ChangeLog (modified) (1 diff)
-
setup.py (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
trunk/babel/messages/extract.py
r375 r376 429 429 elif tok == NAME and value in keywords: 430 430 funcname = value 431 432 def extract_javascript(fileobj, keywords, comment_tags, options): 433 """Extract messages from JavaScript source code. 434 435 :param fileobj: the seekable, file-like object the messages should be 436 extracted from 437 :param keywords: a list of keywords (i.e. function names) that should be 438 recognized as translation functions 439 :param comment_tags: a list of translator tags to search for and include 440 in the results 441 :param options: a dictionary of additional options (optional) 442 :return: an iterator over ``(lineno, funcname, message, comments)`` tuples 443 :rtype: ``iterator`` 444 """ 445 from babel.messages.jslexer import tokenize, unquote_string 446 funcname = message_lineno = None 447 messages = [] 448 last_argument = None 449 translator_comments = [] 450 encoding = options.get('encoding', 'utf-8') 451 last_token = None 452 call_stack = -1 453 454 for token in tokenize(fileobj.read().decode(encoding)): 455 if token.type == 'operator' and token.value == '(': 456 if funcname: 457 message_lineno = token.lineno 458 call_stack += 1 459 460 elif call_stack == -1 and token.type == 'linecomment': 461 value = token.value[2:].strip() 462 if translator_comments and \ 463 translator_comments[-1][0] == token.lineno - 1: 464 translator_comments.append((token.lineno, value)) 465 continue 466 467 for comment_tag in comment_tags: 468 if value.startswith(comment_tag): 469 translator_comments.append((token.lineno, value.strip())) 470 break 471 472 elif token.type == 'multilinecomment': 473 # only one multi-line comment may preceed a translation 474 translator_comments = [] 475 value = token.value[2:-2].strip() 476 for comment_tag in comment_tags: 477 if value.startswith(comment_tag): 478 lines = value.splitlines() 479 if lines: 480 lines[0] = lines[0].strip() 481 lines[1:] = dedent('\n'.join(lines[1:])).splitlines() 482 for offset, line in enumerate(lines): 483 translator_comments.append((token.lineno + offset, 484 line)) 485 break 486 487 elif funcname and call_stack == 0: 488 if token.type == 'operator' and token.value == ')': 489 if last_argument is not None: 490 messages.append(last_argument) 491 if len(messages) > 1: 492 messages = tuple(messages) 493 elif messages: 494 messages = messages[0] 495 else: 496 messages = None 497 498 # Comments don't apply unless they immediately preceed the 499 # message 500 if translator_comments and \ 501 translator_comments[-1][0] < message_lineno - 1: 502 translator_comments = [] 503 504 if messages is not None: 505 yield (message_lineno, funcname, messages, 506 [comment[1] for comment in translator_comments]) 507 508 funcname = message_lineno = last_argument = None 509 translator_comments = [] 510 messages = [] 511 call_stack = -1 512 513 elif token.type == 'string': 514 last_argument = unquote_string(token.value) 515 516 elif token.type == 'operator' and token.value == ',': 517 if last_argument is not None: 518 messages.append(last_argument) 519 last_argument = None 520 else: 521 messages.append(None) 522 523 elif call_stack > 0 and token.type == 'operator' \ 524 and token.value == ')': 525 call_stack -= 1 526 527 elif funcname and call_stack == -1: 528 funcname = None 529 530 elif call_stack == -1 and token.type == 'name' and \ 531 token.value in keywords and \ 532 (last_token is None or last_token.type != 'name' or 533 last_token.value != 'function'): 534 funcname = token.value 535 536 last_token = token -
trunk/babel/messages/tests/extract.py
r375 r376 322 322 u'a prefix too'], messages[1][2]) 323 323 324 class ExtractJavaScriptTestCase(unittest.TestCase): 325 326 def test_simple_extract(self): 327 buf = StringIO("""\ 328 msg1 = _('simple') 329 msg2 = gettext('simple') 330 msg3 = ngettext('s', 'p', 42) 331 """) 332 messages = \ 333 list(extract.extract('javascript', buf, extract.DEFAULT_KEYWORDS, 334 [], {})) 335 336 self.assertEqual([(1, 'simple', []), 337 (2, 'simple', []), 338 (3, ('s', 'p'), [])], messages) 339 340 def test_various_calls(self): 341 buf = StringIO("""\ 342 msg1 = _(i18n_arg.replace(/"/, '"')) 343 msg2 = ungettext(i18n_arg.replace(/"/, '"'), multi_arg.replace(/"/, '"'), 2) 344 msg3 = ungettext("Babel", multi_arg.replace(/"/, '"'), 2) 345 msg4 = ungettext(i18n_arg.replace(/"/, '"'), "Babels", 2) 346 msg5 = ungettext('bunny', 'bunnies', parseInt(Math.random() * 2 + 1)) 347 msg6 = ungettext(arg0, 'bunnies', rparseInt(Math.random() * 2 + 1)) 348 msg7 = _(hello.there) 349 msg8 = gettext('Rabbit') 350 msg9 = dgettext('wiki', model.addPage()) 351 msg10 = dngettext(domain, 'Page', 'Pages', 3) 352 """) 353 messages = \ 354 list(extract.extract('javascript', buf, extract.DEFAULT_KEYWORDS, [], 355 {})) 356 self.assertEqual([(5, (u'bunny', u'bunnies'), []), 357 (8, u'Rabbit', []), 358 (10, (u'Page', u'Pages'), [])], messages) 359 360 def test_message_with_line_comment(self): 361 buf = StringIO("""\ 362 // NOTE: hello 363 msg = _('Bonjour à tous') 364 """) 365 messages = list(extract.extract_javascript(buf, ('_',), ['NOTE:'], {})) 366 self.assertEqual(u'Bonjour à tous', messages[0][2]) 367 self.assertEqual([u'NOTE: hello'], messages[0][3]) 368 369 def test_message_with_multiline_comment(self): 370 buf = StringIO("""\ 371 /* NOTE: hello 372 and bonjour 373 and servus */ 374 msg = _('Bonjour à tous') 375 """) 376 messages = list(extract.extract_javascript(buf, ('_',), ['NOTE:'], {})) 377 self.assertEqual(u'Bonjour à tous', messages[0][2]) 378 self.assertEqual([u'NOTE: hello', 'and bonjour', ' and servus'], messages[0][3]) 379 380 def test_ignore_function_definitions(self): 381 buf = StringIO("""\ 382 function gettext(value) { 383 return translations[language][value] || value; 384 }""") 385 386 messages = list(extract.extract_javascript(buf, ('gettext',), [], {})) 387 self.assertEqual(messages, []) 388 389 def test_misplaced_comments(self): 390 buf = StringIO("""\ 391 /* NOTE: this won't show up */ 392 foo() 393 394 /* NOTE: this will */ 395 msg = _('Something') 396 397 // NOTE: this will show up 398 // too. 399 msg = _('Something else') 400 401 // NOTE: but this won't 402 bar() 403 404 _('no comment here') 405 """) 406 messages = list(extract.extract_javascript(buf, ('_',), ['NOTE:'], {})) 407 self.assertEqual(u'Something', messages[0][2]) 408 self.assertEqual([u'NOTE: this will'], messages[0][3]) 409 self.assertEqual(u'Something else', messages[1][2]) 410 self.assertEqual([u'NOTE: this will show up', 'too.'], messages[1][3]) 411 self.assertEqual(u'no comment here', messages[2][2]) 412 self.assertEqual([], messages[2][3]) 413 324 414 class ExtractTestCase(unittest.TestCase): 325 415 … … 383 473 suite.addTest(doctest.DocTestSuite(extract)) 384 474 suite.addTest(unittest.makeSuite(ExtractPythonTestCase)) 475 suite.addTest(unittest.makeSuite(ExtractJavaScriptTestCase)) 385 476 suite.addTest(unittest.makeSuite(ExtractTestCase)) 386 477 return suite -
trunk/ChangeLog
r375 r376 6 6 * The stripping of the comment tags in comments is optional now and 7 7 is done for each line in a comment. 8 8 * a JavaScript extractor was added. 9 9 10 10 11 Version 0.9.2 -
trunk/setup.py
r291 r376 76 76 ignore = babel.messages.extract:extract_nothing 77 77 python = babel.messages.extract:extract_python 78 javascript = babel.messages.extract:extract_javascript 78 79 """, 79 80
