Exemple #1
0
    def test_no_wrap_and_width_behaviour_on_comments(self):
        catalog = Catalog()
        catalog.add("Pretty dam long message id, which must really be big "
                    "to test this wrap behaviour, if not it won't work.",
                    locations=[("fake.py", n) for n in range(1, 30)])
        buf = BytesIO()
        pofile.write_po(buf, catalog, width=None, omit_header=True)
        self.assertEqual(b"""\
#: fake.py:1 fake.py:2 fake.py:3 fake.py:4 fake.py:5 fake.py:6 fake.py:7
#: fake.py:8 fake.py:9 fake.py:10 fake.py:11 fake.py:12 fake.py:13 fake.py:14
#: fake.py:15 fake.py:16 fake.py:17 fake.py:18 fake.py:19 fake.py:20 fake.py:21
#: fake.py:22 fake.py:23 fake.py:24 fake.py:25 fake.py:26 fake.py:27 fake.py:28
#: fake.py:29
msgid "pretty dam long message id, which must really be big to test this wrap behaviour, if not it won't work."
msgstr ""

""", buf.getvalue().lower())
        buf = BytesIO()
        pofile.write_po(buf, catalog, width=100, omit_header=True)
        self.assertEqual(b"""\
#: fake.py:1 fake.py:2 fake.py:3 fake.py:4 fake.py:5 fake.py:6 fake.py:7 fake.py:8 fake.py:9 fake.py:10
#: fake.py:11 fake.py:12 fake.py:13 fake.py:14 fake.py:15 fake.py:16 fake.py:17 fake.py:18 fake.py:19
#: fake.py:20 fake.py:21 fake.py:22 fake.py:23 fake.py:24 fake.py:25 fake.py:26 fake.py:27 fake.py:28
#: fake.py:29
msgid ""
"pretty dam long message id, which must really be big to test this wrap behaviour, if not it won't"
" work."
msgstr ""

""", buf.getvalue().lower())
Exemple #2
0
    def test_po_with_multiline_obsolete_message(self):
        catalog = Catalog()
        catalog.add(u'foo', u'Voh', locations=[('main.py', 1)])
        msgid = r"""Here's a message that covers
multiple lines, and should still be handled
correctly.
"""
        msgstr = r"""Here's a message that covers
multiple lines, and should still be handled
correctly.
"""
        catalog.obsolete[msgid] = Message(msgid, msgstr,
                                          locations=[('utils.py', 3)])
        buf = BytesIO()
        pofile.write_po(buf, catalog, omit_header=True)
        self.assertEqual(b'''#: main.py:1
msgid "foo"
msgstr "Voh"

#~ msgid ""
#~ "Here's a message that covers\\n"
#~ "multiple lines, and should still be handled\\n"
#~ "correctly.\\n"
#~ msgstr ""
#~ "Here's a message that covers\\n"
#~ "multiple lines, and should still be handled\\n"
#~ "correctly.\\n"''', buf.getvalue().strip())
Exemple #3
0
 def test_write_po_file_with_specified_charset(self):
     catalog = Catalog(charset='iso-8859-1')
     catalog.add('foo', u'äöü', locations=[('main.py', 1)])
     buf = BytesIO()
     pofile.write_po(buf, catalog, omit_header=False)
     po_file = buf.getvalue().strip()
     assert b'"Content-Type: text/plain; charset=iso-8859-1\\n"' in po_file
     assert u'msgstr "äöü"'.encode('iso-8859-1') in po_file
Exemple #4
0
    def test_duplicate_comments(self):
        catalog = Catalog()
        catalog.add(u'foo', auto_comments=['A comment'])
        catalog.add(u'foo', auto_comments=['A comment'])
        buf = BytesIO()
        pofile.write_po(buf, catalog, omit_header=True)
        self.assertEqual(b'''#. A comment
msgid "foo"
msgstr ""''', buf.getvalue().strip())
Exemple #5
0
    def test_join_locations(self):
        catalog = Catalog()
        catalog.add(u'foo', locations=[('main.py', 1)])
        catalog.add(u'foo', locations=[('utils.py', 3)])
        buf = BytesIO()
        pofile.write_po(buf, catalog, omit_header=True)
        self.assertEqual(b'''#: main.py:1 utils.py:3
msgid "foo"
msgstr ""''', buf.getvalue().strip())
Exemple #6
0
    def test_po_with_previous_msgid(self):
        catalog = Catalog()
        catalog.add(u'foo', u'Voh', locations=[('main.py', 1)],
                    previous_id=u'fo')
        buf = BytesIO()
        pofile.write_po(buf, catalog, omit_header=True, include_previous=True)
        self.assertEqual(b'''#: main.py:1
#| msgid "fo"
msgid "foo"
msgstr "Voh"''', buf.getvalue().strip())
Exemple #7
0
    def test_po_with_obsolete_message_ignored(self):
        catalog = Catalog()
        catalog.add(u'foo', u'Voh', locations=[('main.py', 1)])
        catalog.obsolete['bar'] = Message(u'bar', u'Bahr',
                                          locations=[('utils.py', 3)],
                                          user_comments=['User comment'])
        buf = BytesIO()
        pofile.write_po(buf, catalog, omit_header=True, ignore_obsolete=True)
        self.assertEqual(b'''#: main.py:1
msgid "foo"
msgstr "Voh"''', buf.getvalue().strip())
Exemple #8
0
    def test_wrap_locations_with_hyphens(self):
        catalog = Catalog()
        catalog.add(u'foo', locations=[
            ('doupy/templates/base/navmenu.inc.html.py', 60)
        ])
        catalog.add(u'foo', locations=[
            ('doupy/templates/job-offers/helpers.html', 22)
        ])
        buf = BytesIO()
        pofile.write_po(buf, catalog, omit_header=True)
        self.assertEqual(b'''#: doupy/templates/base/navmenu.inc.html.py:60
#: doupy/templates/job-offers/helpers.html:22
msgid "foo"
msgstr ""''', buf.getvalue().strip())
Exemple #9
0
    def test_wrap_long_lines_with_long_word(self):
        text = """Here's some text that
includesareallylongwordthatmightbutshouldnt throw us into an infinite loop
"""
        catalog = Catalog()
        catalog.add(text, locations=[('main.py', 1)])
        buf = BytesIO()
        pofile.write_po(buf, catalog, no_location=True, omit_header=True,
                         width=32)
        self.assertEqual(b'''msgid ""
"Here's some text that\\n"
"includesareallylongwordthatmightbutshouldnt"
" throw us into an infinite "
"loop\\n"
msgstr ""''', buf.getvalue().strip())
Exemple #10
0
    def run(self):
        mappings = self._get_mappings()
        outfile = open(self.output_file, "wb")
        try:
            catalog = Catalog(
                project=self.distribution.get_name(),
                version=self.distribution.get_version(),
                msgid_bugs_address=self.msgid_bugs_address,
                copyright_holder=self.copyright_holder,
                charset=self.charset,
            )

            for dirname, (method_map, options_map) in mappings.items():

                def callback(filename, method, options):
                    if method == "ignore":
                        return
                    filepath = os.path.normpath(os.path.join(dirname, filename))
                    optstr = ""
                    if options:
                        optstr = " (%s)" % ", ".join(['%s="%s"' % (k, v) for k, v in options.items()])
                    log.info("extracting messages from %s%s", filepath, optstr)

                extracted = extract_from_dir(
                    dirname,
                    method_map,
                    options_map,
                    keywords=self._keywords,
                    comment_tags=self._add_comments,
                    callback=callback,
                    strip_comment_tags=self.strip_comments,
                )
                for filename, lineno, message, comments, context in extracted:
                    filepath = os.path.normpath(os.path.join(dirname, filename))
                    catalog.add(message, None, [(filepath, lineno)], auto_comments=comments, context=context)

            log.info("writing PO template file to %s" % self.output_file)
            write_po(
                outfile,
                catalog,
                width=self.width,
                no_location=self.no_location,
                omit_header=self.omit_header,
                sort_output=self.sort_output,
                sort_by_file=self.sort_by_file,
            )
        finally:
            outfile.close()
Exemple #11
0
    def test_pot_with_translator_comments(self):
        catalog = Catalog()
        catalog.add(u'foo', locations=[('main.py', 1)],
                    auto_comments=['Comment About `foo`'])
        catalog.add(u'bar', locations=[('utils.py', 3)],
                    user_comments=['Comment About `bar` with',
                                   'multiple lines.'])
        buf = BytesIO()
        pofile.write_po(buf, catalog, omit_header=True)
        self.assertEqual(b'''#. Comment About `foo`
#: main.py:1
msgid "foo"
msgstr ""

# Comment About `bar` with
# multiple lines.
#: utils.py:3
msgid "bar"
msgstr ""''', buf.getvalue().strip())
Exemple #12
0
    def test_wrap_long_lines(self):
        text = """Here's some text where
white space and line breaks matter, and should

not be removed

"""
        catalog = Catalog()
        catalog.add(text, locations=[('main.py', 1)])
        buf = BytesIO()
        pofile.write_po(buf, catalog, no_location=True, omit_header=True,
                         width=42)
        self.assertEqual(b'''msgid ""
"Here's some text where\\n"
"white space and line breaks matter, and"
" should\\n"
"\\n"
"not be removed\\n"
"\\n"
msgstr ""''', buf.getvalue().strip())
Exemple #13
0
    def test_sorted_po(self):
        catalog = Catalog()
        catalog.add(u'bar', locations=[('utils.py', 3)],
                    user_comments=['Comment About `bar` with',
                                   'multiple lines.'])
        catalog.add((u'foo', u'foos'), (u'Voh', u'Voeh'),
                    locations=[('main.py', 1)])
        buf = BytesIO()
        pofile.write_po(buf, catalog, sort_output=True)
        value = buf.getvalue().strip()
        assert b'''\
# Comment About `bar` with
# multiple lines.
#: utils.py:3
msgid "bar"
msgstr ""

#: main.py:1
msgid "foo"
msgid_plural "foos"
msgstr[0] "Voh"
msgstr[1] "Voeh"''' in value
        assert value.find(b'msgid ""') < value.find(b'msgid "bar"') < value.find(b'msgid "foo"')
Exemple #14
0
def read_mo(fileobj):
    """Read a binary MO file from the given file-like object and return a
    corresponding `Catalog` object.

    :param fileobj: the file-like object to read the MO file from

    :note: The implementation of this function is heavily based on the
           ``GNUTranslations._parse`` method of the ``gettext`` module in the
           standard library.
    """
    catalog = Catalog()
    headers = {}

    filename = getattr(fileobj, 'name', '')

    buf = fileobj.read()
    buflen = len(buf)
    unpack = struct.unpack

    # Parse the .mo file header, which consists of 5 little endian 32
    # bit words.
    magic = unpack('<I', buf[:4])[0] # Are we big endian or little endian?
    if magic == LE_MAGIC:
        version, msgcount, origidx, transidx = unpack('<4I', buf[4:20])
        ii = '<II'
    elif magic == BE_MAGIC:
        version, msgcount, origidx, transidx = unpack('>4I', buf[4:20])
        ii = '>II'
    else:
        raise IOError(0, 'Bad magic number', filename)

    # Now put all messages from the .mo file buffer into the catalog
    # dictionary
    for i in range_type(0, msgcount):
        mlen, moff = unpack(ii, buf[origidx:origidx + 8])
        mend = moff + mlen
        tlen, toff = unpack(ii, buf[transidx:transidx + 8])
        tend = toff + tlen
        if mend < buflen and tend < buflen:
            msg = buf[moff:mend]
            tmsg = buf[toff:tend]
        else:
            raise IOError(0, 'File is corrupt', filename)

        # See if we're looking at GNU .mo conventions for metadata
        if mlen == 0:
            # Catalog description
            lastkey = key = None
            for item in tmsg.splitlines():
                item = item.strip()
                if not item:
                    continue
                if b':' in item:
                    key, value = item.split(b':', 1)
                    lastkey = key = key.strip().lower()
                    headers[key] = value.strip()
                elif lastkey:
                    headers[lastkey] += b'\n' + item

        if b'\x04' in msg: # context
            ctxt, msg = msg.split(b'\x04')
        else:
            ctxt = None

        if b'\x00' in msg: # plural forms
            msg = msg.split(b'\x00')
            tmsg = tmsg.split(b'\x00')
            if catalog.charset:
                msg = [x.decode(catalog.charset) for x in msg]
                tmsg = [x.decode(catalog.charset) for x in tmsg]
        else:
            if catalog.charset:
                msg = msg.decode(catalog.charset)
                tmsg = tmsg.decode(catalog.charset)
        catalog[msg] = Message(msg, tmsg, context=ctxt)

        # advance to next entry in the seek tables
        origidx += 8
        transidx += 8

    catalog.mime_headers = headers.items()
    return catalog
Exemple #15
0
    def extract(self, argv):
        """Subcommand for extracting messages from source files and generating
        a POT file.

        :param argv: the command arguments
        """
        parser = OptionParser(usage=self.usage % ("extract", "dir1 <dir2> ..."), description=self.commands["extract"])
        parser.add_option("--charset", dest="charset", help="charset to use in the output (default " '"%default")')
        parser.add_option(
            "-k",
            "--keyword",
            dest="keywords",
            action="append",
            help="keywords to look for in addition to the "
            "defaults. You can specify multiple -k flags on "
            "the command line.",
        )
        parser.add_option(
            "--no-default-keywords",
            dest="no_default_keywords",
            action="store_true",
            help="do not include the default keywords",
        )
        parser.add_option("--mapping", "-F", dest="mapping_file", help="path to the extraction mapping file")
        parser.add_option(
            "--no-location",
            dest="no_location",
            action="store_true",
            help="do not include location comments with filename " "and line number",
        )
        parser.add_option(
            "--omit-header", dest="omit_header", action="store_true", help='do not include msgid "" entry in header'
        )
        parser.add_option("-o", "--output", dest="output", help="path to the output POT file")
        parser.add_option("-w", "--width", dest="width", type="int", help="set output line width (default 76)")
        parser.add_option(
            "--no-wrap",
            dest="no_wrap",
            action="store_true",
            help="do not break long message lines, longer than " "the output line width, into several lines",
        )
        parser.add_option(
            "--sort-output", dest="sort_output", action="store_true", help="generate sorted output (default False)"
        )
        parser.add_option(
            "--sort-by-file",
            dest="sort_by_file",
            action="store_true",
            help="sort output by file location (default False)",
        )
        parser.add_option(
            "--msgid-bugs-address",
            dest="msgid_bugs_address",
            metavar="EMAIL@ADDRESS",
            help="set report address for msgid",
        )
        parser.add_option("--copyright-holder", dest="copyright_holder", help="set copyright holder in output")
        parser.add_option("--project", dest="project", help="set project name in output")
        parser.add_option("--version", dest="version", help="set project version in output")
        parser.add_option(
            "--add-comments",
            "-c",
            dest="comment_tags",
            metavar="TAG",
            action="append",
            help="place comment block with TAG (or those "
            "preceding keyword lines) in output file. One "
            "TAG per argument call",
        )
        parser.add_option(
            "--strip-comment-tags",
            "-s",
            dest="strip_comment_tags",
            action="store_true",
            help="Strip the comment tags from the comments.",
        )

        parser.set_defaults(
            charset="utf-8",
            keywords=[],
            no_default_keywords=False,
            no_location=False,
            omit_header=False,
            width=None,
            no_wrap=False,
            sort_output=False,
            sort_by_file=False,
            comment_tags=[],
            strip_comment_tags=False,
        )
        options, args = parser.parse_args(argv)
        if not args:
            parser.error("incorrect number of arguments")

        keywords = DEFAULT_KEYWORDS.copy()
        if options.no_default_keywords:
            if not options.keywords:
                parser.error("you must specify new keywords if you disable the " "default ones")
            keywords = {}
        if options.keywords:
            keywords.update(parse_keywords(options.keywords))

        if options.mapping_file:
            fileobj = open(options.mapping_file, "U")
            try:
                method_map, options_map = parse_mapping(fileobj)
            finally:
                fileobj.close()
        else:
            method_map = DEFAULT_MAPPING
            options_map = {}

        if options.width and options.no_wrap:
            parser.error("'--no-wrap' and '--width' are mutually exclusive.")
        elif not options.width and not options.no_wrap:
            options.width = 76

        if options.sort_output and options.sort_by_file:
            parser.error("'--sort-output' and '--sort-by-file' are mutually " "exclusive")

        catalog = Catalog(
            project=options.project,
            version=options.version,
            msgid_bugs_address=options.msgid_bugs_address,
            copyright_holder=options.copyright_holder,
            charset=options.charset,
        )

        for dirname in args:
            if not os.path.isdir(dirname):
                parser.error("%r is not a directory" % dirname)

            def callback(filename, method, options):
                if method == "ignore":
                    return
                filepath = os.path.normpath(os.path.join(dirname, filename))
                optstr = ""
                if options:
                    optstr = " (%s)" % ", ".join(['%s="%s"' % (k, v) for k, v in options.items()])
                self.log.info("extracting messages from %s%s", filepath, optstr)

            extracted = extract_from_dir(
                dirname,
                method_map,
                options_map,
                keywords,
                options.comment_tags,
                callback=callback,
                strip_comment_tags=options.strip_comment_tags,
            )
            for filename, lineno, message, comments, context in extracted:
                filepath = os.path.normpath(os.path.join(dirname, filename))
                catalog.add(message, None, [(filepath, lineno)], auto_comments=comments, context=context)

        catalog_charset = catalog.charset
        if options.output not in (None, "-"):
            self.log.info("writing PO template file to %s" % options.output)
            outfile = open(options.output, "wb")
            close_output = True
        else:
            outfile = sys.stdout

            # This is a bit of a hack on Python 3.  stdout is a text stream so
            # we need to find the underlying file when we write the PO.  In
            # later versions of Babel we want the write_po function to accept
            # text or binary streams and automatically adjust the encoding.
            if not PY2 and hasattr(outfile, "buffer"):
                catalog.charset = outfile.encoding
                outfile = outfile.buffer.raw

            close_output = False

        try:
            write_po(
                outfile,
                catalog,
                width=options.width,
                no_location=options.no_location,
                omit_header=options.omit_header,
                sort_output=options.sort_output,
                sort_by_file=options.sort_by_file,
            )
        finally:
            if close_output:
                outfile.close()
            catalog.charset = catalog_charset