changeset 198:982d7e704fdc

Fix for #35, and a minor improvement to how we parse the catalog fuzzy bit.
author cmlenz
date Tue, 03 Jul 2007 12:52:44 +0000
parents c8c900ec63d8
children e15b7165ced5
files babel/messages/catalog.py babel/messages/frontend.py babel/messages/pofile.py babel/messages/tests/pofile.py
diffstat 4 files changed, 73 insertions(+), 43 deletions(-) [+]
line wrap: on
line diff
--- a/babel/messages/catalog.py
+++ b/babel/messages/catalog.py
@@ -66,8 +66,8 @@
         self.user_comments = list(user_comments)
 
     def __repr__(self):
-        return '<%s %r (Flags: %r)>' % (type(self).__name__, self.id,
-                                       ', '.join([flag for flag in self.flags]))
+        return '<%s %r (flags: %r)>' % (type(self).__name__, self.id,
+                                        list(self.flags))
 
     def fuzzy(self):
         return 'fuzzy' in self.flags
@@ -80,7 +80,7 @@
         >>> msg.fuzzy
         True
         >>> msg
-        <Message 'foo' (Flags: 'fuzzy')>
+        <Message 'foo' (flags: ['fuzzy'])>
         
         :type:  `bool`
         """)
@@ -122,6 +122,7 @@
 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
 #"""
 
+
 class Catalog(object):
     """Representation of a message catalog."""
 
@@ -421,7 +422,7 @@
         >>> catalog = Catalog()
         >>> catalog[u'foo'] = Message(u'foo')
         >>> catalog[u'foo']
-        <Message u'foo' (Flags: '')>
+        <Message u'foo' (flags: [])>
         
         If a message with that ID is already in the catalog, it is updated
         to include the locations and flags of the new message.
@@ -456,6 +457,7 @@
             self.mime_headers = headers.items()
             self.header_comment = '\n'.join(['# %s' % comment for comment
                                              in message.user_comments])
+            self.fuzzy = message.fuzzy
         else:
             if isinstance(id, (list, tuple)):
                 assert isinstance(message.string, (list, tuple))
@@ -468,7 +470,7 @@
         >>> catalog = Catalog()
         >>> catalog.add(u'foo')
         >>> catalog[u'foo']
-        <Message u'foo' (Flags: '')>
+        <Message u'foo' (flags: [])>
         
         This method simply constructs a `Message` object with the given
         arguments and invokes `__setitem__` with that object.
@@ -527,7 +529,7 @@
         >>> 'head' in catalog
         False
         >>> catalog.obsolete.values()
-        [<Message 'head' (Flags: '')>]
+        [<Message 'head' (flags: [])>]
         
         :param template: the reference catalog, usually read from a POT file
         :param fuzzy_matching: whether to use fuzzy matching of message IDs
--- a/babel/messages/frontend.py
+++ b/babel/messages/frontend.py
@@ -471,23 +471,30 @@
         if not self.output_file and not self.output_dir:
             raise DistutilsOptionError('you must specify the output file or '
                                        'directory')
+        if self.output_file and not self.locale:
+            raise DistutilsOptionError('you must specify the locale')
 
     def run(self):
         po_files = []
         if not self.output_file:
             if self.locale:
-                po_files.append(os.path.join(self.output_dir, self.locale,
-                                             'LC_MESSAGES',
-                                             self.domain + '.po'))
+                po_files.append((self.locale,
+                                 os.path.join(self.output_dir, self.locale,
+                                              'LC_MESSAGES',
+                                              self.domain + '.po')))
             else:
                 for locale in os.listdir(self.output_dir):
                     po_file = os.path.join(self.output_dir, locale,
                                            'LC_MESSAGES',
                                            self.domain + '.po')
                     if os.path.exists(po_file):
-                        po_files.append(po_file)
+                        po_files.append((locale, po_file))
         else:
-            po_files.append(self.output_file)
+            po_files.append((self.locale, self.output_file))
+
+        domain = self.domain
+        if not domain:
+            domain = os.path.splitext(os.path.basename(self.input_file))[0]
 
         infile = open(self.input_file, 'U')
         try:
@@ -495,18 +502,18 @@
         finally:
             infile.close()
 
-        for po_file in po_files:
-            log.info('updating catalog %r based on %r', po_file,
+        for locale, filename in po_files:
+            log.info('updating catalog %r based on %r', filename,
                      self.input_file)
-            infile = open(po_file, 'U')
+            infile = open(filename, 'U')
             try:
-                catalog = read_po(infile)
+                catalog = read_po(infile, locale=locale, domain=domain)
             finally:
                 infile.close()
 
             rest = catalog.update(template)
 
-            outfile = open(po_file, 'w')
+            outfile = open(filename, 'w')
             try:
                 write_po(outfile, catalog, ignore_obsolete=self.ignore_obsolete)
             finally:
@@ -867,25 +874,31 @@
 
         if not options.input_file:
             parser.error('you must specify the input file')
-
         if not options.output_file and not options.output_dir:
             parser.error('you must specify the output file or directory')
+        if options.output_file and not options.locale:
+            parser.error('you must specify the loicale')
 
         po_files = []
         if not options.output_file:
             if options.locale:
-                po_files.append(os.path.join(options.output_dir, options.locale,
-                                             'LC_MESSAGES',
-                                             options.domain + '.po'))
+                po_files.append((options.locale,
+                                 os.path.join(options.output_dir,
+                                              options.locale, 'LC_MESSAGES',
+                                              options.domain + '.po')))
             else:
                 for locale in os.listdir(options.output_dir):
                     po_file = os.path.join(options.output_dir, locale,
                                            'LC_MESSAGES',
                                            options.domain + '.po')
                     if os.path.exists(po_file):
-                        po_files.append(po_file)
+                        po_files.append((locale, po_file))
         else:
-            po_files.append(options.output_file)
+            po_files.append((options.locale, options.output_file))
+
+        domain = options.domain
+        if not domain:
+            domain = os.path.splitext(os.path.basename(options.input_file))[0]
 
         infile = open(options.input_file, 'U')
         try:
@@ -893,18 +906,18 @@
         finally:
             infile.close()
 
-        for po_file in po_files:
-            print 'updating catalog %r based on %r' % (po_file,
+        for locale, filename in po_files:
+            print 'updating catalog %r based on %r' % (filename,
                                                        options.input_file)
-            infile = open(po_file, 'U')
+            infile = open(filename, 'U')
             try:
-                catalog = read_po(infile)
+                catalog = read_po(infile, locale=locale, domain=domain)
             finally:
                 infile.close()
 
             rest = catalog.update(template)
 
-            outfile = open(po_file, 'w')
+            outfile = open(filename, 'w')
             try:
                 write_po(outfile, catalog,
                          ignore_obsolete=options.ignore_obsolete)
--- a/babel/messages/pofile.py
+++ b/babel/messages/pofile.py
@@ -83,7 +83,7 @@
     else:
         return unescape(string)
 
-def read_po(fileobj):
+def read_po(fileobj, locale=None, domain=None):
     """Read messages from a ``gettext`` PO (portable object) file from the given
     file-like object and return a `Catalog`.
     
@@ -118,11 +118,16 @@
       ([u'A user comment'], [u'An auto comment'])
     
     :param fileobj: the file-like object to read the PO file from
+    :param locale: the locale identifier or `Locale` object, or `None`
+                   if the catalog is not bound to a locale (which basically
+                   means it's a template)
+    :param domain: the message domain
     :return: an iterator over ``(message, translation, location)`` tuples
     :rtype: ``iterator``
     """
-    catalog = Catalog()
+    catalog = Catalog(locale=locale, domain=domain)
 
+    counter = [0]
     messages = []
     translations = []
     locations = []
@@ -130,8 +135,6 @@
     user_comments = []
     auto_comments = []
     in_msgid = in_msgstr = False
-    fuzzy_header = False
-    in_header = True
 
     def _add_message():
         translations.sort()
@@ -147,6 +150,7 @@
                     list(auto_comments), list(user_comments))
         del messages[:]; del translations[:]; del locations[:];
         del flags[:]; del auto_comments[:]; del user_comments[:]
+        counter[0] += 1
 
     for line in fileobj.readlines():
         line = line.strip().decode(catalog.charset)
@@ -160,12 +164,8 @@
                     locations.append((filename, int(lineno)))
             elif line[1:].startswith(','):
                 for flag in line[2:].lstrip().split(','):
-                    if in_header:
-                        if flag.strip() == 'fuzzy':
-                            fuzzy_header = True
                     flags.append(flag.strip())
-                    
-                    
+
             elif line[1:].startswith('.'):
                 # These are called auto-comments
                 comment = line[2:].strip()
@@ -183,8 +183,6 @@
             elif line.startswith('msgid'):
                 in_msgid = True
                 txt = line[5:].lstrip()
-                if txt == '""':
-                    in_header = True
                 if messages:
                     _add_message()
                 messages.append(txt)
@@ -199,15 +197,20 @@
                     translations.append([0, msg])
             elif line.startswith('"'):
                 if in_msgid:
-                    in_header = False
                     messages[-1] += u'\n' + line.rstrip()
                 elif in_msgstr:
                     translations[-1][1] += u'\n' + line.rstrip()
 
     if messages:
         _add_message()
-        
-    catalog.fuzzy = fuzzy_header
+
+    # No actual messages found, but there was some info in comments, from which
+    # we'll construct an empty header message
+    elif not counter[0] and (flags or user_comments or auto_comments):
+        messages.append(u'')
+        translations.append([0, u''])
+        _add_message()
+
     return catalog
 
 WORD_SEP = re.compile('('
--- a/babel/messages/tests/pofile.py
+++ b/babel/messages/tests/pofile.py
@@ -22,6 +22,18 @@
 
 class ReadPoTestCase(unittest.TestCase):
 
+    def test_preserve_locale(self):
+        buf = StringIO(r'''msgid "foo"
+msgstr "Voh"''')
+        catalog = pofile.read_po(buf, locale='en_US')
+        self.assertEqual('en_US', catalog.locale)
+
+    def test_preserve_domain(self):
+        buf = StringIO(r'''msgid "foo"
+msgstr "Voh"''')
+        catalog = pofile.read_po(buf, domain='mydomain')
+        self.assertEqual('mydomain', catalog.domain)
+
     def test_read_multiline(self):
         buf = StringIO(r'''msgid ""
 "Here's some text that\n"
@@ -35,7 +47,7 @@
         self.assertEqual("Here's some text that\nincludesareallylongwordthat"
                          "mightbutshouldnt throw us into an infinite loop\n",
                          message.id)
-        
+
     def test_fuzzy_header(self):
         buf = StringIO(r'''\
 # Translations template for AReallyReallyLongNameForAProject.
@@ -49,7 +61,7 @@
         catalog = pofile.read_po(buf)
         self.assertEqual(1, len(list(catalog)))
         self.assertEqual(True, list(catalog)[0].fuzzy)
-        
+
     def test_not_fuzzy_header(self):
         buf = StringIO(r'''\
 # Translations template for AReallyReallyLongNameForAProject.
Copyright (C) 2012-2017 Edgewall Software