File indexing completed on 2024-04-21 16:29:14

0001 # -*- coding: UTF-8 -*-
0002 # pology.__init__
0003 
0004 """
0005 The Pology Python library is a package for custom processing of PO files
0006 in field environments. It provides the foundation for Pology end-user tools.
0007 
0008 Core Pology objects -- abstractions of PO catalog and its entries -- are
0009 designed to allow quick writing of robust scripts. By default, the correctness
0010 of processed objects is strictly enforced, but such that the user may easily
0011 switch it off for better performance. Modifications to PO files on disk are
0012 always explicit, and Pology tries to change as few lines as possible to be
0013 friendly to version control systems.
0014 
0015 Pology provides utility various modules for typical processing needs of
0016 different kinds of data in PO files. These include word-splitting,
0017 markup handling, wrapping, comment parsing, summary reporting,
0018 validation, etc.
0019 
0020 Pology also contains language-specific and project-specific modules,
0021 for functionality that is tightly linked to particular languages
0022 and translation projects.
0023 
0024 @author: Chusslove Illich (Часлав Илић) <caslav.ilic@gmx.net>
0025 @author: Sébastien Renard <sebastien.renard@digitalfox.org>
0026 @author: Nicolas Ternisien <nicolas.ternisien@gmail.com>
0027 @author: Goran Rakic (Горан Ракић) <grakic@devbase.net>
0028 @author: Nick Shaforostoff (Николай Шафоростов) <shaforostoff@kde.ru>
0029 
0030 @license: GPLv3
0031 """
0032 
0033 import gettext
0034 import os
0035 import re
0036 
0037 
0038 from pology.colors import ColorString
0039 
0040 
0041 def datadir ():
0042     """
0043     Get data directory of Pology installation.
0044 
0045     @return: absolute directory path
0046     @rtype: string
0047     """
0048 
0049     datadir = "@CONFIG_DATADIR@" # configured if installed
0050     if not os.path.isdir(datadir): # if running from source dir
0051         srcdir = os.path.dirname(os.path.dirname(__file__))
0052         datadir = srcdir
0053     return datadir
0054 
0055 
0056 def localedir ():
0057     """
0058     Get locale directory of Pology installation.
0059 
0060     @return: absolute directory path
0061     @rtype: string
0062     """
0063 
0064     localedir = "@CONFIG_LOCALEDIR@" # configured if installed
0065     if not os.path.isdir(localedir): # if running from source dir
0066         srcdir = os.path.dirname(os.path.dirname(__file__))
0067         localedir = os.path.join(srcdir, "mo")
0068     return localedir
0069 
0070 
0071 def version ():
0072     """
0073     Get Pology version string.
0074 
0075     @return: version string
0076     @rtype: string
0077     """
0078 
0079     verstr = "@CONFIG_VERSION@" # configured if installed
0080     if verstr.startswith("@"): # if running from source dir
0081         try:
0082             verfile = os.path.join(datadir(), "VERSION")
0083             for line in open(verfile, encoding='utf-8'):
0084                 line = line.strip()
0085                 if line:
0086                     verstr = line
0087                     break
0088         except:
0089             pass
0090 
0091     return verstr
0092 
0093 
0094 def version_info ():
0095     """
0096     Get Pology version information.
0097 
0098     Pology version information consists of three version numbers
0099     (major, minor, bugfix) and an arbitrary suffix (may be empty).
0100 
0101     @return: version tuple (major, minor, bugfix, suffix)
0102     @rtype: (int, int, int, string)
0103     """
0104 
0105     verstr = version()
0106     verrx = re.compile(r"^(\d+)\.(\d+)\.?(\d+)?(.*)$")
0107     m = verrx.match(verstr)
0108     major, minor, bugfix = list(map(int, [x or "0" for x in m.groups()[:3]]))
0109     suffix = m.groups()[-1]
0110     verinfo = (major, minor, bugfix, suffix)
0111 
0112     return verinfo
0113 
0114 
0115 # Collect data paths.
0116 
0117 # Setup translations.
0118 try:
0119     _tr = gettext.translation("pology", localedir())
0120 except IOError:
0121     _tr = gettext.NullTranslations()
0122 
0123 
0124 def _ (_ctxt_, _text_, **kwargs):
0125     """
0126     Get translation of the text into user's language.
0127 
0128     If there are any formatting directives in the text,
0129     they should be named;
0130     the arguments which substitute them are given
0131     as keyword values following the text.
0132 
0133     @param _ctxt_: the context in which the text is used
0134     @type _ctxt_: string
0135     @param _text_: the text to translate
0136     @type _text_: string
0137     @return: translated text if available, otherwise original
0138     @rtype: L{ColorString<colors.ColorString>}
0139     """
0140 
0141     ts = TextTrans()
0142     ts._init(_ctxt_, _text_, None, kwargs)
0143     return ts.to_string()
0144 
0145 
0146 def n_ (_ctxt_, _stext_, _ptext_, **kwargs):
0147     """
0148     Get translation of the singular/plural text into user's language.
0149 
0150     If there are any formatting directives in the text,
0151     they should be named;
0152     the arguments which substitute them are given
0153     as keyword values following the text.
0154 
0155     The plural deciding number is given by the C{num} keyword argument.
0156     If no such key exists, or its value is not an integer, an error is raised.
0157 
0158     @param _ctxt_: the context in which the text is used
0159     @type _ctxt_: string
0160     @param _stext_: the text to translate for the singular case
0161     @type _stext_: string
0162     @param _ptext_: the text to translate for the plural case
0163     @type _ptext_: string
0164     @return: translated text if available, otherwise original
0165     @rtype: L{ColorString<colors.ColorString>}
0166     """
0167 
0168     ts = TextTrans()
0169     ts._init(_ctxt_, _stext_, _ptext_, kwargs)
0170     return ts.to_string()
0171 
0172 
0173 def t_ (_ctxt_, _text_, **kwargs):
0174     """
0175     Get deferred translation of the text into user's language.
0176 
0177     Like L{_()<_>}, but returns deferred translation object
0178     instead of translated text as string.
0179     In this way some or all arguments for named formatting directives
0180     can be supplied at a later point, using L{with_args<TextTrans.with_args>}
0181     method, and then the translated string obtained
0182     by L{to_string<TextTrans.to_string>} method.
0183 
0184     @returns: deferred translation
0185     @rtype: L{TextTrans}
0186     """
0187 
0188     ts = TextTrans()
0189     ts._init(_ctxt_, _text_, None, kwargs)
0190     return ts
0191 
0192 
0193 def tn_ (_ctxt_, _stext_, _ptext_, **kwargs):
0194     """
0195     Get deferred translation of the singular/plural text into user's language.
0196 
0197     Like L{n_()<_>}, but returns deferred translation object
0198     instead of translated text as string.
0199     In this way some or all arguments for named formatting directives
0200     can be supplied at a later point, using L{with_args<TextTrans.with_args>}
0201     method, and then the translated string obtained
0202     by L{to_string<TextTrans.to_string>} method.
0203 
0204     @returns: deferred translation
0205     @rtype: L{TextTrans}
0206     """
0207 
0208     ts = TextTrans()
0209     ts._init(_ctxt_, _stext_, _ptext_, kwargs)
0210     return ts
0211 
0212 
0213 class TextTrans:
0214     """
0215     Class for intermediate handling of translated user-visible text.
0216 
0217     Objects of this type are not functional if created manually,
0218     but only through C{t*_()} translation calls.
0219     """
0220 
0221     def _init (self, msgctxt, msgid, msgid_plural, kwargs):
0222 
0223         self._msgctxt = msgctxt
0224         self._msgid = msgid
0225         self._msgid_plural = msgid_plural
0226         self._kwargs = kwargs
0227 
0228 
0229     def _copy (self):
0230 
0231         # Shallow copy all attributes.
0232         t = TextTrans()
0233         t._msgctxt = self._msgctxt
0234         t._msgid = self._msgid
0235         t._msgid_plural = self._msgid_plural
0236         t._kwargs = dict(self._kwargs)
0237         return t
0238 
0239 
0240     def with_args (self, **kwargs):
0241         """
0242         Add arguments for substitution in the text, creating new object.
0243 
0244         @returns: new deferred translation
0245         @rtype: L{TextTrans}
0246         """
0247 
0248         t = self._copy()
0249         t._kwargs.update(kwargs)
0250         return t
0251 
0252 
0253     def to_string (self):
0254         """
0255         Translate the text to get ordinary string.
0256 
0257         @returns: translated text
0258         @rtype: L{ColorString<colors.ColorString>}
0259         """
0260 
0261         if self._msgid_plural is None:
0262             trf = _tr.gettext # camouflaged against xgettext
0263             if self._msgctxt is None:
0264                 msgstr = trf(self._msgid)
0265             else:
0266                 msgstr = trf("%s\x04%s" % (self._msgctxt, self._msgid))
0267                 if "\x04" in msgstr:
0268                     msgstr = self._msgid
0269         else:
0270             n = self._kwargs.get("num")
0271             if n is None or not isinstance(n, int):
0272                 raise PologyError(
0273                     _("@info",
0274                       "No '%(arg)s' keyword argument to "
0275                       "plural translation request.",
0276                       arg="num"))
0277             trf = _tr.ngettext # camouflaged against xgettext
0278             if self._msgctxt is None:
0279                 msgstr = trf(self._msgid, self._msgid_plural, n)
0280             else:
0281                 msgstr = trf("%s\x04%s" % (self._msgctxt, self._msgid),
0282                              self._msgid_plural, n)
0283                 if "\x04" in msgstr:
0284                     msgstr = self._msgid
0285 
0286         msgstr = ColorString(msgstr) # before substituting arguments
0287         msgstr = msgstr % self._kwargs
0288 
0289         return msgstr
0290 
0291 
0292 class PologyError (Exception):
0293     """
0294     Base exception class for errors in Pology.
0295     """
0296 
0297     def __init__ (self, msg):
0298         """
0299         Constructor.
0300 
0301         @param msg: a description of what went wrong
0302         @type msg: string
0303         """
0304 
0305         self._msg = msg
0306 
0307 
0308     def  __str__(self):
0309         return str(self._msg)