i18n/polib.py
changeset 43076 2372284d9457
parent 40185 19fc5a986669
child 43977 04e0e0e73892
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
    15 
    15 
    16 from __future__ import absolute_import
    16 from __future__ import absolute_import
    17 
    17 
    18 __author__ = 'David Jean Louis <izimobil@gmail.com>'
    18 __author__ = 'David Jean Louis <izimobil@gmail.com>'
    19 __version__ = '1.0.7'
    19 __version__ = '1.0.7'
    20 __all__ = ['pofile', 'POFile', 'POEntry', 'mofile', 'MOFile', 'MOEntry',
    20 __all__ = [
    21            'default_encoding', 'escape', 'unescape', 'detect_encoding', ]
    21     'pofile',
       
    22     'POFile',
       
    23     'POEntry',
       
    24     'mofile',
       
    25     'MOFile',
       
    26     'MOEntry',
       
    27     'default_encoding',
       
    28     'escape',
       
    29     'unescape',
       
    30     'detect_encoding',
       
    31 ]
    22 
    32 
    23 import array
    33 import array
    24 import codecs
    34 import codecs
    25 import os
    35 import os
    26 import re
    36 import re
    53         return s
    63         return s
    54 
    64 
    55     def u(s):
    65     def u(s):
    56         return unicode(s, "unicode_escape")
    66         return unicode(s, "unicode_escape")
    57 
    67 
       
    68 
    58 else:
    69 else:
    59     PY3 = True
    70     PY3 = True
    60     text_type = str
    71     text_type = str
    61 
    72 
    62     def b(s):
    73     def b(s):
    63         return s.encode("latin-1")
    74         return s.encode("latin-1")
    64 
    75 
    65     def u(s):
    76     def u(s):
    66         return s
    77         return s
       
    78 
       
    79 
    67 # }}}
    80 # }}}
    68 # _pofile_or_mofile {{{
    81 # _pofile_or_mofile {{{
    69 
    82 
    70 
    83 
    71 def _pofile_or_mofile(f, type, **kwargs):
    84 def _pofile_or_mofile(f, type, **kwargs):
    82     kls = type == 'pofile' and _POFileParser or _MOFileParser
    95     kls = type == 'pofile' and _POFileParser or _MOFileParser
    83     parser = kls(
    96     parser = kls(
    84         f,
    97         f,
    85         encoding=enc,
    98         encoding=enc,
    86         check_for_duplicates=kwargs.get('check_for_duplicates', False),
    99         check_for_duplicates=kwargs.get('check_for_duplicates', False),
    87         klass=kwargs.get('klass')
   100         klass=kwargs.get('klass'),
    88     )
   101     )
    89     instance = parser.parse()
   102     instance = parser.parse()
    90     instance.wrapwidth = kwargs.get('wrapwidth', 78)
   103     instance.wrapwidth = kwargs.get('wrapwidth', 78)
    91     return instance
   104     return instance
       
   105 
       
   106 
    92 # }}}
   107 # }}}
    93 # _is_file {{{
   108 # _is_file {{{
    94 
   109 
    95 
   110 
    96 def _is_file(filename_or_contents):
   111 def _is_file(filename_or_contents):
   105     """
   120     """
   106     try:
   121     try:
   107         return os.path.exists(filename_or_contents)
   122         return os.path.exists(filename_or_contents)
   108     except (ValueError, UnicodeEncodeError):
   123     except (ValueError, UnicodeEncodeError):
   109         return False
   124         return False
       
   125 
       
   126 
   110 # }}}
   127 # }}}
   111 # function pofile() {{{
   128 # function pofile() {{{
   112 
   129 
   113 
   130 
   114 def pofile(pofile, **kwargs):
   131 def pofile(pofile, **kwargs):
   137         class which is used to instantiate the return value (optional,
   154         class which is used to instantiate the return value (optional,
   138         default: ``None``, the return value with be a :class:`~polib.POFile`
   155         default: ``None``, the return value with be a :class:`~polib.POFile`
   139         instance).
   156         instance).
   140     """
   157     """
   141     return _pofile_or_mofile(pofile, 'pofile', **kwargs)
   158     return _pofile_or_mofile(pofile, 'pofile', **kwargs)
       
   159 
       
   160 
   142 # }}}
   161 # }}}
   143 # function mofile() {{{
   162 # function mofile() {{{
   144 
   163 
   145 
   164 
   146 def mofile(mofile, **kwargs):
   165 def mofile(mofile, **kwargs):
   170         class which is used to instantiate the return value (optional,
   189         class which is used to instantiate the return value (optional,
   171         default: ``None``, the return value with be a :class:`~polib.POFile`
   190         default: ``None``, the return value with be a :class:`~polib.POFile`
   172         instance).
   191         instance).
   173     """
   192     """
   174     return _pofile_or_mofile(mofile, 'mofile', **kwargs)
   193     return _pofile_or_mofile(mofile, 'mofile', **kwargs)
       
   194 
       
   195 
   175 # }}}
   196 # }}}
   176 # function detect_encoding() {{{
   197 # function detect_encoding() {{{
   177 
   198 
   178 
   199 
   179 def detect_encoding(file, binary_mode=False):
   200 def detect_encoding(file, binary_mode=False):
   227                     enc = enc.decode('utf-8')
   248                     enc = enc.decode('utf-8')
   228                 if charset_exists(enc):
   249                 if charset_exists(enc):
   229                     return enc
   250                     return enc
   230         f.close()
   251         f.close()
   231     return default_encoding
   252     return default_encoding
       
   253 
       
   254 
   232 # }}}
   255 # }}}
   233 # function escape() {{{
   256 # function escape() {{{
   234 
   257 
   235 
   258 
   236 def escape(st):
   259 def escape(st):
   237     """
   260     """
   238     Escapes the characters ``\\\\``, ``\\t``, ``\\n``, ``\\r`` and ``"`` in
   261     Escapes the characters ``\\\\``, ``\\t``, ``\\n``, ``\\r`` and ``"`` in
   239     the given string ``st`` and returns it.
   262     the given string ``st`` and returns it.
   240     """
   263     """
   241     return st.replace('\\', r'\\')\
   264     return (
   242              .replace('\t', r'\t')\
   265         st.replace('\\', r'\\')
   243              .replace('\r', r'\r')\
   266         .replace('\t', r'\t')
   244              .replace('\n', r'\n')\
   267         .replace('\r', r'\r')
   245              .replace('\"', r'\"')
   268         .replace('\n', r'\n')
       
   269         .replace('\"', r'\"')
       
   270     )
       
   271 
       
   272 
   246 # }}}
   273 # }}}
   247 # function unescape() {{{
   274 # function unescape() {{{
   248 
   275 
   249 
   276 
   250 def unescape(st):
   277 def unescape(st):
   251     """
   278     """
   252     Unescapes the characters ``\\\\``, ``\\t``, ``\\n``, ``\\r`` and ``"`` in
   279     Unescapes the characters ``\\\\``, ``\\t``, ``\\n``, ``\\r`` and ``"`` in
   253     the given string ``st`` and returns it.
   280     the given string ``st`` and returns it.
   254     """
   281     """
       
   282 
   255     def unescape_repl(m):
   283     def unescape_repl(m):
   256         m = m.group(1)
   284         m = m.group(1)
   257         if m == 'n':
   285         if m == 'n':
   258             return '\n'
   286             return '\n'
   259         if m == 't':
   287         if m == 't':
   261         if m == 'r':
   289         if m == 'r':
   262             return '\r'
   290             return '\r'
   263         if m == '\\':
   291         if m == '\\':
   264             return '\\'
   292             return '\\'
   265         return m  # handles escaped double quote
   293         return m  # handles escaped double quote
       
   294 
   266     return re.sub(r'\\(\\|n|t|r|")', unescape_repl, st)
   295     return re.sub(r'\\(\\|n|t|r|")', unescape_repl, st)
       
   296 
       
   297 
   267 # }}}
   298 # }}}
   268 # class _BaseFile {{{
   299 # class _BaseFile {{{
   269 
   300 
   270 
   301 
   271 class _BaseFile(list):
   302 class _BaseFile(list):
   315     def __unicode__(self):
   346     def __unicode__(self):
   316         """
   347         """
   317         Returns the unicode representation of the file.
   348         Returns the unicode representation of the file.
   318         """
   349         """
   319         ret = []
   350         ret = []
   320         entries = [self.metadata_as_entry()] + \
   351         entries = [self.metadata_as_entry()] + [
   321                   [e for e in self if not e.obsolete]
   352             e for e in self if not e.obsolete
       
   353         ]
   322         for entry in entries:
   354         for entry in entries:
   323             ret.append(entry.__unicode__(self.wrapwidth))
   355             ret.append(entry.__unicode__(self.wrapwidth))
   324         for entry in self.obsolete_entries():
   356         for entry in self.obsolete_entries():
   325             ret.append(entry.__unicode__(self.wrapwidth))
   357             ret.append(entry.__unicode__(self.wrapwidth))
   326         ret = u('\n').join(ret)
   358         ret = u('\n').join(ret)
   327 
   359 
   328         assert isinstance(ret, text_type)
   360         assert isinstance(ret, text_type)
   329         #if type(ret) != text_type:
   361         # if type(ret) != text_type:
   330         #    return unicode(ret, self.encoding)
   362         #    return unicode(ret, self.encoding)
   331         return ret
   363         return ret
   332 
   364 
   333     if PY3:
   365     if PY3:
       
   366 
   334         def __str__(self):
   367         def __str__(self):
   335             return self.__unicode__()
   368             return self.__unicode__()
       
   369 
   336     else:
   370     else:
       
   371 
   337         def __str__(self):
   372         def __str__(self):
   338             """
   373             """
   339             Returns the string representation of the file.
   374             Returns the string representation of the file.
   340             """
   375             """
   341             return unicode(self).encode(self.encoding)
   376             return unicode(self).encode(self.encoding)
   351         Argument:
   386         Argument:
   352 
   387 
   353         ``entry``
   388         ``entry``
   354             an instance of :class:`~polib._BaseEntry`.
   389             an instance of :class:`~polib._BaseEntry`.
   355         """
   390         """
   356         return self.find(entry.msgid, by='msgid', msgctxt=entry.msgctxt) \
   391         return (
       
   392             self.find(entry.msgid, by='msgid', msgctxt=entry.msgctxt)
   357             is not None
   393             is not None
       
   394         )
   358 
   395 
   359     def __eq__(self, other):
   396     def __eq__(self, other):
   360         return str(self) == str(other)
   397         return str(self) == str(other)
   361 
   398 
   362     def append(self, entry):
   399     def append(self, entry):
   437         fhandle.close()
   474         fhandle.close()
   438         # set the file path if not set
   475         # set the file path if not set
   439         if self.fpath is None and fpath:
   476         if self.fpath is None and fpath:
   440             self.fpath = fpath
   477             self.fpath = fpath
   441 
   478 
   442     def find(self, st, by='msgid', include_obsolete_entries=False,
   479     def find(
   443              msgctxt=False):
   480         self, st, by='msgid', include_obsolete_entries=False, msgctxt=False
       
   481     ):
   444         """
   482         """
   445         Find the entry which msgid (or property identified by the ``by``
   483         Find the entry which msgid (or property identified by the ``by``
   446         argument) matches the string ``st``.
   484         argument) matches the string ``st``.
   447 
   485 
   448         Keyword arguments:
   486         Keyword arguments:
   488             'Language-Team',
   526             'Language-Team',
   489             'MIME-Version',
   527             'MIME-Version',
   490             'Content-Type',
   528             'Content-Type',
   491             'Content-Transfer-Encoding',
   529             'Content-Transfer-Encoding',
   492             'Language',
   530             'Language',
   493             'Plural-Forms'
   531             'Plural-Forms',
   494         ]
   532         ]
   495         ordered_data = []
   533         ordered_data = []
   496         for data in data_order:
   534         for data in data_order:
   497             try:
   535             try:
   498                 value = metadata.pop(data)
   536                 value = metadata.pop(data)
   522                 return 1
   560                 return 1
   523             elif self_msgid < other_msgid:
   561             elif self_msgid < other_msgid:
   524                 return -1
   562                 return -1
   525             else:
   563             else:
   526                 return 0
   564                 return 0
       
   565 
   527         # add metadata entry
   566         # add metadata entry
   528         entries.sort(key=lambda o: o.msgctxt or o.msgid)
   567         entries.sort(key=lambda o: o.msgctxt or o.msgid)
   529         mentry = self.metadata_as_entry()
   568         mentry = self.metadata_as_entry()
   530         #mentry.msgstr = mentry.msgstr.replace('\\n', '').lstrip()
   569         # mentry.msgstr = mentry.msgstr.replace('\\n', '').lstrip()
   531         entries = [mentry] + entries
   570         entries = [mentry] + entries
   532         entries_len = len(entries)
   571         entries_len = len(entries)
   533         ids, strs = b(''), b('')
   572         ids, strs = b(''), b('')
   534         for e in entries:
   573         for e in entries:
   535             # For each string, we need size and file offset.  Each string is
   574             # For each string, we need size and file offset.  Each string is
   576             # start of key index
   615             # start of key index
   577             7 * 4,
   616             7 * 4,
   578             # start of value index
   617             # start of value index
   579             7 * 4 + entries_len * 8,
   618             7 * 4 + entries_len * 8,
   580             # size and offset of hash table, we don't use hash tables
   619             # size and offset of hash table, we don't use hash tables
   581             0, keystart
   620             0,
   582 
   621             keystart,
   583         )
   622         )
   584         if PY3 and sys.version_info.minor > 1:  # python 3.2 or superior
   623         if PY3 and sys.version_info.minor > 1:  # python 3.2 or superior
   585             output += array.array("i", offsets).tobytes()
   624             output += array.array("i", offsets).tobytes()
   586         else:
   625         else:
   587             output += array.array("i", offsets).tostring()
   626             output += array.array("i", offsets).tostring()
   595         only if it's an unicode string and returns the encoded string.
   634         only if it's an unicode string and returns the encoded string.
   596         """
   635         """
   597         if isinstance(mixed, text_type):
   636         if isinstance(mixed, text_type):
   598             mixed = mixed.encode(self.encoding)
   637             mixed = mixed.encode(self.encoding)
   599         return mixed
   638         return mixed
       
   639 
       
   640 
   600 # }}}
   641 # }}}
   601 # class POFile {{{
   642 # class POFile {{{
   602 
   643 
   603 
   644 
   604 class POFile(_BaseFile):
   645 class POFile(_BaseFile):
   656 
   697 
   657     def untranslated_entries(self):
   698     def untranslated_entries(self):
   658         """
   699         """
   659         Convenience method that returns the list of untranslated entries.
   700         Convenience method that returns the list of untranslated entries.
   660         """
   701         """
   661         return [e for e in self if not e.translated() and not e.obsolete
   702         return [
   662                 and not 'fuzzy' in e.flags]
   703             e
       
   704             for e in self
       
   705             if not e.translated() and not e.obsolete and not 'fuzzy' in e.flags
       
   706         ]
   663 
   707 
   664     def fuzzy_entries(self):
   708     def fuzzy_entries(self):
   665         """
   709         """
   666         Convenience method that returns the list of fuzzy entries.
   710         Convenience method that returns the list of fuzzy entries.
   667         """
   711         """
   701             e.merge(entry)
   745             e.merge(entry)
   702         # ok, now we must "obsolete" entries that are not in the refpot anymore
   746         # ok, now we must "obsolete" entries that are not in the refpot anymore
   703         for entry in self:
   747         for entry in self:
   704             if entry.msgid not in refpot_msgids:
   748             if entry.msgid not in refpot_msgids:
   705                 entry.obsolete = True
   749                 entry.obsolete = True
       
   750 
       
   751 
   706 # }}}
   752 # }}}
   707 # class MOFile {{{
   753 # class MOFile {{{
   708 
   754 
   709 
   755 
   710 class MOFile(_BaseFile):
   756 class MOFile(_BaseFile):
   711     """
   757     """
   712     Mo file reader/writer.
   758     Mo file reader/writer.
   713     This class inherits the :class:`~polib._BaseFile` class and, by
   759     This class inherits the :class:`~polib._BaseFile` class and, by
   714     extension, the python ``list`` type.
   760     extension, the python ``list`` type.
   715     """
   761     """
   716     MAGIC = 0x950412de
   762 
   717     MAGIC_SWAPPED = 0xde120495
   763     MAGIC = 0x950412DE
       
   764     MAGIC_SWAPPED = 0xDE120495
   718 
   765 
   719     def __init__(self, *args, **kwargs):
   766     def __init__(self, *args, **kwargs):
   720         """
   767         """
   721         Constructor, accepts all keywords arguments accepted by
   768         Constructor, accepts all keywords arguments accepted by
   722         :class:`~polib._BaseFile` class.
   769         :class:`~polib._BaseFile` class.
   774     def obsolete_entries(self):
   821     def obsolete_entries(self):
   775         """
   822         """
   776         Convenience method to keep the same interface with POFile instances.
   823         Convenience method to keep the same interface with POFile instances.
   777         """
   824         """
   778         return []
   825         return []
       
   826 
       
   827 
   779 # }}}
   828 # }}}
   780 # class _BaseEntry {{{
   829 # class _BaseEntry {{{
   781 
   830 
   782 
   831 
   783 class _BaseEntry(object):
   832 class _BaseEntry(object):
   829         else:
   878         else:
   830             delflag = ''
   879             delflag = ''
   831         ret = []
   880         ret = []
   832         # write the msgctxt if any
   881         # write the msgctxt if any
   833         if self.msgctxt is not None:
   882         if self.msgctxt is not None:
   834             ret += self._str_field("msgctxt", delflag, "", self.msgctxt,
   883             ret += self._str_field(
   835                                    wrapwidth)
   884                 "msgctxt", delflag, "", self.msgctxt, wrapwidth
       
   885             )
   836         # write the msgid
   886         # write the msgid
   837         ret += self._str_field("msgid", delflag, "", self.msgid, wrapwidth)
   887         ret += self._str_field("msgid", delflag, "", self.msgid, wrapwidth)
   838         # write the msgid_plural if any
   888         # write the msgid_plural if any
   839         if self.msgid_plural:
   889         if self.msgid_plural:
   840             ret += self._str_field("msgid_plural", delflag, "",
   890             ret += self._str_field(
   841                                    self.msgid_plural, wrapwidth)
   891                 "msgid_plural", delflag, "", self.msgid_plural, wrapwidth
       
   892             )
   842         if self.msgstr_plural:
   893         if self.msgstr_plural:
   843             # write the msgstr_plural if any
   894             # write the msgstr_plural if any
   844             msgstrs = self.msgstr_plural
   895             msgstrs = self.msgstr_plural
   845             keys = list(msgstrs)
   896             keys = list(msgstrs)
   846             keys.sort()
   897             keys.sort()
   847             for index in keys:
   898             for index in keys:
   848                 msgstr = msgstrs[index]
   899                 msgstr = msgstrs[index]
   849                 plural_index = '[%s]' % index
   900                 plural_index = '[%s]' % index
   850                 ret += self._str_field("msgstr", delflag, plural_index, msgstr,
   901                 ret += self._str_field(
   851                                        wrapwidth)
   902                     "msgstr", delflag, plural_index, msgstr, wrapwidth
       
   903                 )
   852         else:
   904         else:
   853             # otherwise write the msgstr
   905             # otherwise write the msgstr
   854             ret += self._str_field("msgstr", delflag, "", self.msgstr,
   906             ret += self._str_field(
   855                                    wrapwidth)
   907                 "msgstr", delflag, "", self.msgstr, wrapwidth
       
   908             )
   856         ret.append('')
   909         ret.append('')
   857         ret = u('\n').join(ret)
   910         ret = u('\n').join(ret)
   858         return ret
   911         return ret
   859 
   912 
   860     if PY3:
   913     if PY3:
       
   914 
   861         def __str__(self):
   915         def __str__(self):
   862             return self.__unicode__()
   916             return self.__unicode__()
       
   917 
   863     else:
   918     else:
       
   919 
   864         def __str__(self):
   920         def __str__(self):
   865             """
   921             """
   866             Returns the string representation of the entry.
   922             Returns the string representation of the entry.
   867             """
   923             """
   868             return unicode(self).encode(self.encoding)
   924             return unicode(self).encode(self.encoding)
   869 
   925 
   870     def __eq__(self, other):
   926     def __eq__(self, other):
   871         return str(self) == str(other)
   927         return str(self) == str(other)
   872 
   928 
   873     def _str_field(self, fieldname, delflag, plural_index, field,
   929     def _str_field(self, fieldname, delflag, plural_index, field, wrapwidth=78):
   874                    wrapwidth=78):
       
   875         lines = field.splitlines(True)
   930         lines = field.splitlines(True)
   876         if len(lines) > 1:
   931         if len(lines) > 1:
   877             lines = [''] + lines  # start with initial empty line
   932             lines = [''] + lines  # start with initial empty line
   878         else:
   933         else:
   879             escaped_field = escape(field)
   934             escaped_field = escape(field)
   886             if plural_index:
   941             if plural_index:
   887                 flength += len(plural_index)
   942                 flength += len(plural_index)
   888             real_wrapwidth = wrapwidth - flength + specialchars_count
   943             real_wrapwidth = wrapwidth - flength + specialchars_count
   889             if wrapwidth > 0 and len(field) > real_wrapwidth:
   944             if wrapwidth > 0 and len(field) > real_wrapwidth:
   890                 # Wrap the line but take field name into account
   945                 # Wrap the line but take field name into account
   891                 lines = [''] + [unescape(item) for item in wrap(
   946                 lines = [''] + [
   892                     escaped_field,
   947                     unescape(item)
   893                     wrapwidth - 2,  # 2 for quotes ""
   948                     for item in wrap(
   894                     drop_whitespace=False,
   949                         escaped_field,
   895                     break_long_words=False
   950                         wrapwidth - 2,  # 2 for quotes ""
   896                 )]
   951                         drop_whitespace=False,
       
   952                         break_long_words=False,
       
   953                     )
       
   954                 ]
   897             else:
   955             else:
   898                 lines = [field]
   956                 lines = [field]
   899         if fieldname.startswith('previous_'):
   957         if fieldname.startswith('previous_'):
   900             # quick and dirty trick to get the real field name
   958             # quick and dirty trick to get the real field name
   901             fieldname = fieldname[9:]
   959             fieldname = fieldname[9:]
   902 
   960 
   903         ret = ['%s%s%s "%s"' % (delflag, fieldname, plural_index,
   961         ret = [
   904                                 escape(lines.pop(0)))]
   962             '%s%s%s "%s"'
       
   963             % (delflag, fieldname, plural_index, escape(lines.pop(0)))
       
   964         ]
   905         for line in lines:
   965         for line in lines:
   906             ret.append('%s"%s"' % (delflag, escape(line)))
   966             ret.append('%s"%s"' % (delflag, escape(line)))
   907         return ret
   967         return ret
       
   968 
       
   969 
   908 # }}}
   970 # }}}
   909 # class POEntry {{{
   971 # class POEntry {{{
   910 
   972 
   911 
   973 
   912 class POEntry(_BaseEntry):
   974 class POEntry(_BaseEntry):
   970                         ret += wrap(
  1032                         ret += wrap(
   971                             comment,
  1033                             comment,
   972                             wrapwidth,
  1034                             wrapwidth,
   973                             initial_indent=c[1],
  1035                             initial_indent=c[1],
   974                             subsequent_indent=c[1],
  1036                             subsequent_indent=c[1],
   975                             break_long_words=False
  1037                             break_long_words=False,
   976                         )
  1038                         )
   977                     else:
  1039                     else:
   978                         ret.append('%s%s' % (c[1], comment))
  1040                         ret.append('%s%s' % (c[1], comment))
   979 
  1041 
   980         # occurrences (with text wrapping as xgettext does)
  1042         # occurrences (with text wrapping as xgettext does)
   989             if wrapwidth > 0 and len(filestr) + 3 > wrapwidth:
  1051             if wrapwidth > 0 and len(filestr) + 3 > wrapwidth:
   990                 # textwrap split words that contain hyphen, this is not
  1052                 # textwrap split words that contain hyphen, this is not
   991                 # what we want for filenames, so the dirty hack is to
  1053                 # what we want for filenames, so the dirty hack is to
   992                 # temporally replace hyphens with a char that a file cannot
  1054                 # temporally replace hyphens with a char that a file cannot
   993                 # contain, like "*"
  1055                 # contain, like "*"
   994                 ret += [l.replace('*', '-') for l in wrap(
  1056                 ret += [
   995                     filestr.replace('-', '*'),
  1057                     l.replace('*', '-')
   996                     wrapwidth,
  1058                     for l in wrap(
   997                     initial_indent='#: ',
  1059                         filestr.replace('-', '*'),
   998                     subsequent_indent='#: ',
  1060                         wrapwidth,
   999                     break_long_words=False
  1061                         initial_indent='#: ',
  1000                 )]
  1062                         subsequent_indent='#: ',
       
  1063                         break_long_words=False,
       
  1064                     )
       
  1065                 ]
  1001             else:
  1066             else:
  1002                 ret.append('#: ' + filestr)
  1067                 ret.append('#: ' + filestr)
  1003 
  1068 
  1004         # flags (TODO: wrapping ?)
  1069         # flags (TODO: wrapping ?)
  1005         if self.flags:
  1070         if self.flags:
  1006             ret.append('#, %s' % ', '.join(self.flags))
  1071             ret.append('#, %s' % ', '.join(self.flags))
  1007 
  1072 
  1008         # previous context and previous msgid/msgid_plural
  1073         # previous context and previous msgid/msgid_plural
  1009         fields = ['previous_msgctxt', 'previous_msgid',
  1074         fields = ['previous_msgctxt', 'previous_msgid', 'previous_msgid_plural']
  1010                   'previous_msgid_plural']
       
  1011         for f in fields:
  1075         for f in fields:
  1012             val = getattr(self, f)
  1076             val = getattr(self, f)
  1013             if val:
  1077             if val:
  1014                 ret += self._str_field(f, "#| ", "", val, wrapwidth)
  1078                 ret += self._str_field(f, "#| ", "", val, wrapwidth)
  1015 
  1079 
  1016         ret.append(_BaseEntry.__unicode__(self, wrapwidth))
  1080         ret.append(_BaseEntry.__unicode__(self, wrapwidth))
  1017         ret = u('\n').join(ret)
  1081         ret = u('\n').join(ret)
  1018 
  1082 
  1019         assert isinstance(ret, text_type)
  1083         assert isinstance(ret, text_type)
  1020         #if type(ret) != types.UnicodeType:
  1084         # if type(ret) != types.UnicodeType:
  1021         #    return unicode(ret, self.encoding)
  1085         #    return unicode(ret, self.encoding)
  1022         return ret
  1086         return ret
  1023 
  1087 
  1024     def __cmp__(self, other):
  1088     def __cmp__(self, other):
  1025         """
  1089         """
  1129                 except KeyError:
  1193                 except KeyError:
  1130                     self.msgstr_plural[pos] = ''
  1194                     self.msgstr_plural[pos] = ''
  1131 
  1195 
  1132     def __hash__(self):
  1196     def __hash__(self):
  1133         return hash((self.msgid, self.msgstr))
  1197         return hash((self.msgid, self.msgstr))
       
  1198 
       
  1199 
  1134 # }}}
  1200 # }}}
  1135 # class MOEntry {{{
  1201 # class MOEntry {{{
  1136 
  1202 
  1137 
  1203 
  1138 class MOEntry(_BaseEntry):
  1204 class MOEntry(_BaseEntry):
  1139     """
  1205     """
  1140     Represents a mo file entry.
  1206     Represents a mo file entry.
  1141     """
  1207     """
       
  1208 
  1142     def __init__(self, *args, **kwargs):
  1209     def __init__(self, *args, **kwargs):
  1143         """
  1210         """
  1144         Constructor, accepts the following keyword arguments,
  1211         Constructor, accepts the following keyword arguments,
  1145         for consistency with :class:`~polib.POEntry`:
  1212         for consistency with :class:`~polib.POEntry`:
  1146 
  1213 
  1165         self.previous_msgid = None
  1232         self.previous_msgid = None
  1166         self.previous_msgid_plural = None
  1233         self.previous_msgid_plural = None
  1167 
  1234 
  1168     def __hash__(self):
  1235     def __hash__(self):
  1169         return hash((self.msgid, self.msgstr))
  1236         return hash((self.msgid, self.msgstr))
       
  1237 
  1170 
  1238 
  1171 # }}}
  1239 # }}}
  1172 # class _POFileParser {{{
  1240 # class _POFileParser {{{
  1173 
  1241 
  1174 
  1242 
  1209         if klass is None:
  1277         if klass is None:
  1210             klass = POFile
  1278             klass = POFile
  1211         self.instance = klass(
  1279         self.instance = klass(
  1212             pofile=pofile,
  1280             pofile=pofile,
  1213             encoding=enc,
  1281             encoding=enc,
  1214             check_for_duplicates=kwargs.get('check_for_duplicates', False)
  1282             check_for_duplicates=kwargs.get('check_for_duplicates', False),
  1215         )
  1283         )
  1216         self.transitions = {}
  1284         self.transitions = {}
  1217         self.current_line = 0
  1285         self.current_line = 0
  1218         self.current_entry = POEntry(linenum=self.current_line)
  1286         self.current_entry = POEntry(linenum=self.current_line)
  1219         self.current_state = 'st'
  1287         self.current_state = 'st'
  1236         #     * MI: a msgid
  1304         #     * MI: a msgid
  1237         #     * MP: a msgid plural
  1305         #     * MP: a msgid plural
  1238         #     * MS: a msgstr
  1306         #     * MS: a msgstr
  1239         #     * MX: a msgstr plural
  1307         #     * MX: a msgstr plural
  1240         #     * MC: a msgid or msgstr continuation line
  1308         #     * MC: a msgid or msgstr continuation line
  1241         all = ['st', 'he', 'gc', 'oc', 'fl', 'ct', 'pc', 'pm', 'pp', 'tc',
  1309         all = [
  1242                'ms', 'mp', 'mx', 'mi']
  1310             'st',
  1243 
  1311             'he',
  1244         self.add('tc', ['st', 'he'],                                     'he')
  1312             'gc',
  1245         self.add('tc', ['gc', 'oc', 'fl', 'tc', 'pc', 'pm', 'pp', 'ms',
  1313             'oc',
  1246                         'mp', 'mx', 'mi'],                               'tc')
  1314             'fl',
  1247         self.add('gc', all,                                              'gc')
  1315             'ct',
  1248         self.add('oc', all,                                              'oc')
  1316             'pc',
  1249         self.add('fl', all,                                              'fl')
  1317             'pm',
  1250         self.add('pc', all,                                              'pc')
  1318             'pp',
  1251         self.add('pm', all,                                              'pm')
  1319             'tc',
  1252         self.add('pp', all,                                              'pp')
  1320             'ms',
  1253         self.add('ct', ['st', 'he', 'gc', 'oc', 'fl', 'tc', 'pc', 'pm',
  1321             'mp',
  1254                         'pp', 'ms', 'mx'],                               'ct')
  1322             'mx',
  1255         self.add('mi', ['st', 'he', 'gc', 'oc', 'fl', 'ct', 'tc', 'pc',
  1323             'mi',
  1256                  'pm', 'pp', 'ms', 'mx'],                                'mi')
  1324         ]
  1257         self.add('mp', ['tc', 'gc', 'pc', 'pm', 'pp', 'mi'],             'mp')
  1325 
  1258         self.add('ms', ['mi', 'mp', 'tc'],                               'ms')
  1326         self.add('tc', ['st', 'he'], 'he')
  1259         self.add('mx', ['mi', 'mx', 'mp', 'tc'],                         'mx')
  1327         self.add(
       
  1328             'tc',
       
  1329             ['gc', 'oc', 'fl', 'tc', 'pc', 'pm', 'pp', 'ms', 'mp', 'mx', 'mi'],
       
  1330             'tc',
       
  1331         )
       
  1332         self.add('gc', all, 'gc')
       
  1333         self.add('oc', all, 'oc')
       
  1334         self.add('fl', all, 'fl')
       
  1335         self.add('pc', all, 'pc')
       
  1336         self.add('pm', all, 'pm')
       
  1337         self.add('pp', all, 'pp')
       
  1338         self.add(
       
  1339             'ct',
       
  1340             ['st', 'he', 'gc', 'oc', 'fl', 'tc', 'pc', 'pm', 'pp', 'ms', 'mx'],
       
  1341             'ct',
       
  1342         )
       
  1343         self.add(
       
  1344             'mi',
       
  1345             [
       
  1346                 'st',
       
  1347                 'he',
       
  1348                 'gc',
       
  1349                 'oc',
       
  1350                 'fl',
       
  1351                 'ct',
       
  1352                 'tc',
       
  1353                 'pc',
       
  1354                 'pm',
       
  1355                 'pp',
       
  1356                 'ms',
       
  1357                 'mx',
       
  1358             ],
       
  1359             'mi',
       
  1360         )
       
  1361         self.add('mp', ['tc', 'gc', 'pc', 'pm', 'pp', 'mi'], 'mp')
       
  1362         self.add('ms', ['mi', 'mp', 'tc'], 'ms')
       
  1363         self.add('mx', ['mi', 'mx', 'mp', 'tc'], 'mx')
  1260         self.add('mc', ['ct', 'mi', 'mp', 'ms', 'mx', 'pm', 'pp', 'pc'], 'mc')
  1364         self.add('mc', ['ct', 'mi', 'mp', 'ms', 'mx', 'pm', 'pp', 'pc'], 'mc')
  1261 
  1365 
  1262     def parse(self):
  1366     def parse(self):
  1263         """
  1367         """
  1264         Run the state machine, parse the file line by line and call process()
  1368         Run the state machine, parse the file line by line and call process()
  1298                 self.entry_obsolete = 0
  1402                 self.entry_obsolete = 0
  1299 
  1403 
  1300             # Take care of keywords like
  1404             # Take care of keywords like
  1301             # msgid, msgid_plural, msgctxt & msgstr.
  1405             # msgid, msgid_plural, msgctxt & msgstr.
  1302             if tokens[0] in keywords and nb_tokens > 1:
  1406             if tokens[0] in keywords and nb_tokens > 1:
  1303                 line = line[len(tokens[0]):].lstrip()
  1407                 line = line[len(tokens[0]) :].lstrip()
  1304                 if re.search(r'([^\\]|^)"', line[1:-1]):
  1408                 if re.search(r'([^\\]|^)"', line[1:-1]):
  1305                     raise IOError('Syntax error in po file %s (line %s): '
  1409                     raise IOError(
  1306                                   'unescaped double quote found' %
  1410                         'Syntax error in po file %s (line %s): '
  1307                                   (self.instance.fpath, self.current_line))
  1411                         'unescaped double quote found'
       
  1412                         % (self.instance.fpath, self.current_line)
       
  1413                     )
  1308                 self.current_token = line
  1414                 self.current_token = line
  1309                 self.process(keywords[tokens[0]])
  1415                 self.process(keywords[tokens[0]])
  1310                 continue
  1416                 continue
  1311 
  1417 
  1312             self.current_token = line
  1418             self.current_token = line
  1318                 self.process('oc')
  1424                 self.process('oc')
  1319 
  1425 
  1320             elif line[:1] == '"':
  1426             elif line[:1] == '"':
  1321                 # we are on a continuation line
  1427                 # we are on a continuation line
  1322                 if re.search(r'([^\\]|^)"', line[1:-1]):
  1428                 if re.search(r'([^\\]|^)"', line[1:-1]):
  1323                     raise IOError('Syntax error in po file %s (line %s): '
  1429                     raise IOError(
  1324                                   'unescaped double quote found' %
  1430                         'Syntax error in po file %s (line %s): '
  1325                                   (self.instance.fpath, self.current_line))
  1431                         'unescaped double quote found'
       
  1432                         % (self.instance.fpath, self.current_line)
       
  1433                     )
  1326                 self.process('mc')
  1434                 self.process('mc')
  1327 
  1435 
  1328             elif line[:7] == 'msgstr[':
  1436             elif line[:7] == 'msgstr[':
  1329                 # we are on a msgstr plural
  1437                 # we are on a msgstr plural
  1330                 self.process('mx')
  1438                 self.process('mx')
  1347                 # we are on a generated comment line
  1455                 # we are on a generated comment line
  1348                 self.process('gc')
  1456                 self.process('gc')
  1349 
  1457 
  1350             elif tokens[0] == '#|':
  1458             elif tokens[0] == '#|':
  1351                 if nb_tokens <= 1:
  1459                 if nb_tokens <= 1:
  1352                     raise IOError('Syntax error in po file %s (line %s)' %
  1460                     raise IOError(
  1353                                   (self.instance.fpath, self.current_line))
  1461                         'Syntax error in po file %s (line %s)'
       
  1462                         % (self.instance.fpath, self.current_line)
       
  1463                     )
  1354 
  1464 
  1355                 # Remove the marker and any whitespace right after that.
  1465                 # Remove the marker and any whitespace right after that.
  1356                 line = line[2:].lstrip()
  1466                 line = line[2:].lstrip()
  1357                 self.current_token = line
  1467                 self.current_token = line
  1358 
  1468 
  1361                     self.process('mc')
  1471                     self.process('mc')
  1362                     continue
  1472                     continue
  1363 
  1473 
  1364                 if nb_tokens == 2:
  1474                 if nb_tokens == 2:
  1365                     # Invalid continuation line.
  1475                     # Invalid continuation line.
  1366                     raise IOError('Syntax error in po file %s (line %s): '
  1476                     raise IOError(
  1367                                   'invalid continuation line' %
  1477                         'Syntax error in po file %s (line %s): '
  1368                                   (self.instance.fpath, self.current_line))
  1478                         'invalid continuation line'
       
  1479                         % (self.instance.fpath, self.current_line)
       
  1480                     )
  1369 
  1481 
  1370                 # we are on a "previous translation" comment line,
  1482                 # we are on a "previous translation" comment line,
  1371                 if tokens[1] not in prev_keywords:
  1483                 if tokens[1] not in prev_keywords:
  1372                     # Unknown keyword in previous translation comment.
  1484                     # Unknown keyword in previous translation comment.
  1373                     raise IOError('Syntax error in po file %s (line %s): '
  1485                     raise IOError(
  1374                                   'unknown keyword %s' %
  1486                         'Syntax error in po file %s (line %s): '
  1375                                   (self.instance.fpath, self.current_line,
  1487                         'unknown keyword %s'
  1376                                    tokens[1]))
  1488                         % (self.instance.fpath, self.current_line, tokens[1])
       
  1489                     )
  1377 
  1490 
  1378                 # Remove the keyword and any whitespace
  1491                 # Remove the keyword and any whitespace
  1379                 # between it and the starting quote.
  1492                 # between it and the starting quote.
  1380                 line = line[len(tokens[1]):].lstrip()
  1493                 line = line[len(tokens[1]) :].lstrip()
  1381                 self.current_token = line
  1494                 self.current_token = line
  1382                 self.process(prev_keywords[tokens[1]])
  1495                 self.process(prev_keywords[tokens[1]])
  1383 
  1496 
  1384             else:
  1497             else:
  1385                 raise IOError('Syntax error in po file %s (line %s)' %
  1498                 raise IOError(
  1386                               (self.instance.fpath, self.current_line))
  1499                     'Syntax error in po file %s (line %s)'
  1387 
  1500                     % (self.instance.fpath, self.current_line)
  1388         if self.current_entry and len(tokens) > 0 and \
  1501                 )
  1389            not tokens[0].startswith('#'):
  1502 
       
  1503         if (
       
  1504             self.current_entry
       
  1505             and len(tokens) > 0
       
  1506             and not tokens[0].startswith('#')
       
  1507         ):
  1390             # since entries are added when another entry is found, we must add
  1508             # since entries are added when another entry is found, we must add
  1391             # the last entry here (only if there are lines). Trailing comments
  1509             # the last entry here (only if there are lines). Trailing comments
  1392             # are ignored
  1510             # are ignored
  1393             self.instance.append(self.current_entry)
  1511             self.instance.append(self.current_entry)
  1394 
  1512 
  1447         try:
  1565         try:
  1448             (action, state) = self.transitions[(symbol, self.current_state)]
  1566             (action, state) = self.transitions[(symbol, self.current_state)]
  1449             if action():
  1567             if action():
  1450                 self.current_state = state
  1568                 self.current_state = state
  1451         except Exception:
  1569         except Exception:
  1452             raise IOError('Syntax error in po file (line %s)' %
  1570             raise IOError(
  1453                           self.current_line)
  1571                 'Syntax error in po file (line %s)' % self.current_line
       
  1572             )
  1454 
  1573 
  1455     # state handlers
  1574     # state handlers
  1456 
  1575 
  1457     def handle_he(self):
  1576     def handle_he(self):
  1458         """Handle a header comment."""
  1577         """Handle a header comment."""
  1505     def handle_fl(self):
  1624     def handle_fl(self):
  1506         """Handle a flags line."""
  1625         """Handle a flags line."""
  1507         if self.current_state in ['mc', 'ms', 'mx']:
  1626         if self.current_state in ['mc', 'ms', 'mx']:
  1508             self.instance.append(self.current_entry)
  1627             self.instance.append(self.current_entry)
  1509             self.current_entry = POEntry(linenum=self.current_line)
  1628             self.current_entry = POEntry(linenum=self.current_line)
  1510         self.current_entry.flags += [c.strip() for c in
  1629         self.current_entry.flags += [
  1511                                      self.current_token[3:].split(',')]
  1630             c.strip() for c in self.current_token[3:].split(',')
       
  1631         ]
  1512         return True
  1632         return True
  1513 
  1633 
  1514     def handle_pp(self):
  1634     def handle_pp(self):
  1515         """Handle a previous msgid_plural line."""
  1635         """Handle a previous msgid_plural line."""
  1516         if self.current_state in ['mc', 'ms', 'mx']:
  1636         if self.current_state in ['mc', 'ms', 'mx']:
  1517             self.instance.append(self.current_entry)
  1637             self.instance.append(self.current_entry)
  1518             self.current_entry = POEntry(linenum=self.current_line)
  1638             self.current_entry = POEntry(linenum=self.current_line)
  1519         self.current_entry.previous_msgid_plural = \
  1639         self.current_entry.previous_msgid_plural = unescape(
  1520             unescape(self.current_token[1:-1])
  1640             self.current_token[1:-1]
       
  1641         )
  1521         return True
  1642         return True
  1522 
  1643 
  1523     def handle_pm(self):
  1644     def handle_pm(self):
  1524         """Handle a previous msgid line."""
  1645         """Handle a previous msgid line."""
  1525         if self.current_state in ['mc', 'ms', 'mx']:
  1646         if self.current_state in ['mc', 'ms', 'mx']:
  1526             self.instance.append(self.current_entry)
  1647             self.instance.append(self.current_entry)
  1527             self.current_entry = POEntry(linenum=self.current_line)
  1648             self.current_entry = POEntry(linenum=self.current_line)
  1528         self.current_entry.previous_msgid = \
  1649         self.current_entry.previous_msgid = unescape(self.current_token[1:-1])
  1529             unescape(self.current_token[1:-1])
       
  1530         return True
  1650         return True
  1531 
  1651 
  1532     def handle_pc(self):
  1652     def handle_pc(self):
  1533         """Handle a previous msgctxt line."""
  1653         """Handle a previous msgctxt line."""
  1534         if self.current_state in ['mc', 'ms', 'mx']:
  1654         if self.current_state in ['mc', 'ms', 'mx']:
  1535             self.instance.append(self.current_entry)
  1655             self.instance.append(self.current_entry)
  1536             self.current_entry = POEntry(linenum=self.current_line)
  1656             self.current_entry = POEntry(linenum=self.current_line)
  1537         self.current_entry.previous_msgctxt = \
  1657         self.current_entry.previous_msgctxt = unescape(self.current_token[1:-1])
  1538             unescape(self.current_token[1:-1])
       
  1539         return True
  1658         return True
  1540 
  1659 
  1541     def handle_ct(self):
  1660     def handle_ct(self):
  1542         """Handle a msgctxt."""
  1661         """Handle a msgctxt."""
  1543         if self.current_state in ['mc', 'ms', 'mx']:
  1662         if self.current_state in ['mc', 'ms', 'mx']:
  1566         return True
  1685         return True
  1567 
  1686 
  1568     def handle_mx(self):
  1687     def handle_mx(self):
  1569         """Handle a msgstr plural."""
  1688         """Handle a msgstr plural."""
  1570         index = self.current_token[7]
  1689         index = self.current_token[7]
  1571         value = self.current_token[self.current_token.find('"') + 1:-1]
  1690         value = self.current_token[self.current_token.find('"') + 1 : -1]
  1572         self.current_entry.msgstr_plural[int(index)] = unescape(value)
  1691         self.current_entry.msgstr_plural[int(index)] = unescape(value)
  1573         self.msgstr_index = int(index)
  1692         self.msgstr_index = int(index)
  1574         return True
  1693         return True
  1575 
  1694 
  1576     def handle_mc(self):
  1695     def handle_mc(self):
  1592             self.current_entry.previous_msgid += token
  1711             self.current_entry.previous_msgid += token
  1593         elif self.current_state == 'pc':
  1712         elif self.current_state == 'pc':
  1594             self.current_entry.previous_msgctxt += token
  1713             self.current_entry.previous_msgctxt += token
  1595         # don't change the current state
  1714         # don't change the current state
  1596         return False
  1715         return False
       
  1716 
       
  1717 
  1597 # }}}
  1718 # }}}
  1598 # class _MOFileParser {{{
  1719 # class _MOFileParser {{{
  1599 
  1720 
  1600 
  1721 
  1601 class _MOFileParser(object):
  1722 class _MOFileParser(object):
  1626         if klass is None:
  1747         if klass is None:
  1627             klass = MOFile
  1748             klass = MOFile
  1628         self.instance = klass(
  1749         self.instance = klass(
  1629             fpath=mofile,
  1750             fpath=mofile,
  1630             encoding=kwargs.get('encoding', default_encoding),
  1751             encoding=kwargs.get('encoding', default_encoding),
  1631             check_for_duplicates=kwargs.get('check_for_duplicates', False)
  1752             check_for_duplicates=kwargs.get('check_for_duplicates', False),
  1632         )
  1753         )
  1633 
  1754 
  1634     def __del__(self):
  1755     def __del__(self):
  1635         """
  1756         """
  1636         Make sure the file is closed, this prevents warnings on unclosed file
  1757         Make sure the file is closed, this prevents warnings on unclosed file
  1697             msgid_tokens = msgid.split(b('\0'))
  1818             msgid_tokens = msgid.split(b('\0'))
  1698             if len(msgid_tokens) > 1:
  1819             if len(msgid_tokens) > 1:
  1699                 entry = self._build_entry(
  1820                 entry = self._build_entry(
  1700                     msgid=msgid_tokens[0],
  1821                     msgid=msgid_tokens[0],
  1701                     msgid_plural=msgid_tokens[1],
  1822                     msgid_plural=msgid_tokens[1],
  1702                     msgstr_plural=dict((k, v) for k, v in
  1823                     msgstr_plural=dict(
  1703                                        enumerate(msgstr.split(b('\0'))))
  1824                         (k, v) for k, v in enumerate(msgstr.split(b('\0')))
       
  1825                     ),
  1704                 )
  1826                 )
  1705             else:
  1827             else:
  1706                 entry = self._build_entry(msgid=msgid, msgstr=msgstr)
  1828                 entry = self._build_entry(msgid=msgid, msgstr=msgstr)
  1707             self.instance.append(entry)
  1829             self.instance.append(entry)
  1708         # close opened file
  1830         # close opened file
  1709         self.fhandle.close()
  1831         self.fhandle.close()
  1710         return self.instance
  1832         return self.instance
  1711 
  1833 
  1712     def _build_entry(self, msgid, msgstr=None, msgid_plural=None,
  1834     def _build_entry(
  1713                      msgstr_plural=None):
  1835         self, msgid, msgstr=None, msgid_plural=None, msgstr_plural=None
       
  1836     ):
  1714         msgctxt_msgid = msgid.split(b('\x04'))
  1837         msgctxt_msgid = msgid.split(b('\x04'))
  1715         encoding = self.instance.encoding
  1838         encoding = self.instance.encoding
  1716         if len(msgctxt_msgid) > 1:
  1839         if len(msgctxt_msgid) > 1:
  1717             kwargs = {
  1840             kwargs = {
  1718                 'msgctxt': msgctxt_msgid[0].decode(encoding),
  1841                 'msgctxt': msgctxt_msgid[0].decode(encoding),
  1738         bytes = self.fhandle.read(numbytes)
  1861         bytes = self.fhandle.read(numbytes)
  1739         tup = struct.unpack(fmt, bytes)
  1862         tup = struct.unpack(fmt, bytes)
  1740         if len(tup) == 1:
  1863         if len(tup) == 1:
  1741             return tup[0]
  1864             return tup[0]
  1742         return tup
  1865         return tup
       
  1866 
       
  1867 
  1743 # }}}
  1868 # }}}
  1744 # class TextWrapper {{{
  1869 # class TextWrapper {{{
  1745 
  1870 
  1746 
  1871 
  1747 class TextWrapper(textwrap.TextWrapper):
  1872 class TextWrapper(textwrap.TextWrapper):
  1748     """
  1873     """
  1749     Subclass of textwrap.TextWrapper that backport the
  1874     Subclass of textwrap.TextWrapper that backport the
  1750     drop_whitespace option.
  1875     drop_whitespace option.
  1751     """
  1876     """
       
  1877 
  1752     def __init__(self, *args, **kwargs):
  1878     def __init__(self, *args, **kwargs):
  1753         drop_whitespace = kwargs.pop('drop_whitespace', True)
  1879         drop_whitespace = kwargs.pop('drop_whitespace', True)
  1754         textwrap.TextWrapper.__init__(self, *args, **kwargs)
  1880         textwrap.TextWrapper.__init__(self, *args, **kwargs)
  1755         self.drop_whitespace = drop_whitespace
  1881         self.drop_whitespace = drop_whitespace
  1756 
  1882 
  1821             # of all lines (return value).
  1947             # of all lines (return value).
  1822             if cur_line:
  1948             if cur_line:
  1823                 lines.append(indent + ''.join(cur_line))
  1949                 lines.append(indent + ''.join(cur_line))
  1824 
  1950 
  1825         return lines
  1951         return lines
       
  1952 
       
  1953 
  1826 # }}}
  1954 # }}}
  1827 # function wrap() {{{
  1955 # function wrap() {{{
  1828 
  1956 
  1829 
  1957 
  1830 def wrap(text, width=70, **kwargs):
  1958 def wrap(text, width=70, **kwargs):
  1833     """
  1961     """
  1834     if sys.version_info < (2, 6):
  1962     if sys.version_info < (2, 6):
  1835         return TextWrapper(width=width, **kwargs).wrap(text)
  1963         return TextWrapper(width=width, **kwargs).wrap(text)
  1836     return textwrap.wrap(text, width=width, **kwargs)
  1964     return textwrap.wrap(text, width=width, **kwargs)
  1837 
  1965 
       
  1966 
  1838 # }}}
  1967 # }}}