File indexing completed on 2024-12-01 13:47:51
0001 # -*- coding: UTF-8 -*- 0002 0003 """ 0004 Message entries in PO catalogs. 0005 0006 Classes from this module define the entries proper, 0007 while the header entry is handled by L{pology.header}. 0008 0009 @see: L{pology.header} 0010 0011 @author: Chusslove Illich (Часлав Илић) <caslav.ilic@gmx.net> 0012 @license: GPLv3 0013 """ 0014 0015 from pology.colors import ColorString, cjoin 0016 from pology.escape import escape_c 0017 from pology.wrap import wrap_field, wrap_comment, wrap_comment_unwrap 0018 from pology.monitored import Monitored, Monlist, Monset, Monpair 0019 0020 0021 _Message_spec = { 0022 "manual_comment" : {"type" : Monlist, 0023 "spec" : {"*" : {"type" : str}}}, 0024 "auto_comment" : {"type" : Monlist, 0025 "spec" : {"*" : {"type" : str}}}, 0026 "source" : {"type" : Monlist, 0027 "spec" : {"*" : {"type" : Monpair, 0028 "spec" : {"first" : {"type" : str}, 0029 "second" : {"type" : int}}}}}, 0030 "flag" : {"type" : Monset, 0031 "spec" : {"*" : {"type" : str}}}, 0032 0033 "obsolete" : {"type" : bool}, 0034 0035 "msgctxt_previous" : {"type" : (str, type(None))}, 0036 "msgid_previous" : {"type" : (str, type(None))}, 0037 "msgid_plural_previous" : {"type" : (str, type(None))}, 0038 0039 "msgctxt" : {"type" : (str, type(None))}, 0040 "msgid" : {"type" : str}, 0041 "msgid_plural" : {"type" : (str, type(None))}, 0042 "msgstr" : {"type" : Monlist, 0043 "spec" : {"*" : {"type" : str}}}, 0044 0045 "key" : {"type" : str, "derived" : True}, 0046 "fmt" : {"type" : str, "derived" : True}, 0047 "inv" : {"type" : str, "derived" : True}, 0048 "trn" : {"type" : str, "derived" : True}, 0049 "fuzzy" : {"type" : bool}, 0050 "untranslated" : {"type" : bool, "derived" : True}, 0051 "translated" : {"type" : bool, "derived" : True}, 0052 "active" : {"type" : bool, "derived" : True}, 0053 "format" : {"type" : str, "derived" : True}, 0054 0055 "refline" : {"type" : int}, 0056 "refentry" : {"type" : int}, 0057 } 0058 0059 # Exclusive groupings. 0060 _Message_single_fields = ( 0061 "msgctxt_previous", "msgid_previous", "msgid_plural_previous", 0062 "msgctxt", "msgid", "msgid_plural", 0063 ) 0064 _Message_list_fields = ( 0065 "manual_comment", "auto_comment", 0066 "msgstr", 0067 ) 0068 _Message_list2_fields = ( 0069 "source", 0070 ) 0071 _Message_set_fields = ( 0072 "flag", 0073 ) 0074 _Message_state_fields = ( 0075 "fuzzy", "obsolete", 0076 ) 0077 0078 # Convenience groupings. 0079 _Message_all_fields = (() 0080 + _Message_single_fields 0081 + _Message_list_fields 0082 + _Message_list2_fields 0083 + _Message_set_fields 0084 + _Message_state_fields 0085 ) 0086 _Message_sequence_fields = (() 0087 + _Message_list_fields 0088 + _Message_list2_fields 0089 + _Message_set_fields 0090 ) 0091 _Message_key_fields = ( 0092 "msgctxt", "msgid", 0093 ) 0094 _Message_mandatory_fields = ( 0095 "msgid", "msgstr", 0096 ) 0097 _Message_currprev_fields = ( 0098 ("msgctxt", "msgctxt_previous"), 0099 ("msgid", "msgid_previous"), 0100 ("msgid_plural", "msgid_plural_previous"), 0101 ) 0102 _Message_fmt_fields = ( 0103 "msgctxt", 0104 "msgid", 0105 "msgid_plural", 0106 "msgstr", 0107 "obsolete", 0108 "fuzzy", 0109 ) 0110 _Message_inv_fields = ( 0111 "obsolete", 0112 "fuzzy", 0113 "manual_comment", 0114 "msgctxt_previous", 0115 "msgid_previous", 0116 "msgid_plural_previous", 0117 "msgctxt", 0118 "msgid", 0119 "msgid_plural", 0120 "msgstr", 0121 ) 0122 0123 0124 def _escape (text): 0125 0126 text = escape_c(text) 0127 if isinstance(text, ColorString): 0128 text = text.replace(""", "\\"") 0129 return text 0130 0131 0132 class Message_base (object): 0133 """ 0134 Abstract base class for entries in PO catalogs. 0135 0136 Elements of the message are accessed through instance attributes. 0137 Some of them are read-only, typically those that are derived from the 0138 normal read-write attributes and cannot be set independently. 0139 0140 The precise type of each attribute depends on the subclass through which 0141 it is accessed, but has a general behavior of one of the standard types. 0142 E.g. when the behavior is that of a list, the type is stated as C{list*}. 0143 All strings are assumed unicode, except where noted otherwise. 0144 0145 Regardless of the exact composition of the message, each message object 0146 will have all the instance attributes listed. In case the message actually 0147 does not have an element corresponding to an instance attribute, 0148 that attribute will have an appropriate null value. 0149 0150 Only the read-only attributes are provided by this base class, 0151 while the read-write attributes are to be provided by its subclasses. 0152 All are listed here, however, as the interface that all subclasses 0153 should implement. 0154 0155 @ivar manual_comment: manual (translator) comments (C{# ...}) 0156 @type manual_comment: list* of strings 0157 0158 @ivar auto_comment: automatic (extracted) comments (C{#. ...}) 0159 @type auto_comment: list* of strings 0160 0161 @ivar source: source references, as filepath:lineno pairs (C{#: ...}) 0162 @type source: list* of pairs* 0163 0164 @ivar flag: message flags (C{#, ...}) 0165 @type flag: set* of strings 0166 0167 @ivar obsolete: whether entry is obsolete (C{#~ ...}) 0168 @type obsolete: bool 0169 0170 @ivar msgctxt_previous: previous context field (C{#| msgctxt "..."}) 0171 @type msgctxt_previous: string or None 0172 0173 @ivar msgid_previous: previous message field (C{#| msgid "..."}) 0174 @type msgid_previous: string or None 0175 0176 @ivar msgid_plural_previous: 0177 previous plural field (C{#| msgid_plural "..."}) 0178 @type msgid_plural_previous: string or None 0179 0180 @ivar msgctxt: context field (C{msgctxt "..."}) 0181 @type msgctxt: string or None 0182 0183 @ivar msgid: message field (C{msgid "..."}) 0184 @type msgid: string 0185 0186 @ivar msgid_plural: plural field (C{msgid_plural "..."}) 0187 @type msgid_plural: string or None 0188 0189 @ivar msgstr: translation fields (C{msgstr "..."}, C{msgstr[n] "..."}) 0190 @type msgstr: list* of strings 0191 0192 @ivar key: (read-only) key composition 0193 0194 Message key is formed by the parts of the message which define 0195 unique entry in a catalog. 0196 0197 The value is an undefined serialization of C{msgctxt} and C{msgid}. 0198 @type key: string 0199 0200 @ivar fmt: (read-only) format composition 0201 0202 Format composition consists of all message parts which determine 0203 contents of compiled message in the MO file, including whether 0204 it is compiled at all. 0205 0206 The value is an undefined serialization of: C{msgctxt}, C{msgid}, 0207 C{msgid_plural}, C{msgstr}, C{fuzzy}, C{obsolete}. 0208 @type fmt: string 0209 0210 @ivar inv: (read-only) extraction-invariant composition 0211 0212 Extraction-invariant parts of the message are those that are not 0213 dependent on the placement and comments to the message in the code. 0214 In effect, these are the parts which are not eliminated when 0215 the message is obsoleted after merging. 0216 0217 The value is an undefined serialization of: C{msgctxt}, C{msgid}, 0218 C{msgid_plural}, C{msgstr}, C{fuzzy}, C{obsolete}, C{manual_comment}, 0219 C{msgctxt_previous}, C{msgid_previous}, C{msgid_plural_previous}. 0220 @type inv: string 0221 0222 @ivar trn: (read-only) translator-controlled composition 0223 0224 Translator-controlled parts of the message are those that are 0225 normally modified by a translator when working on a PO file. 0226 0227 The value is an undefined serialization of: C{msgstr}, C{fuzzy}, 0228 C{manual_comment}. 0229 @type trn: string 0230 0231 @ivar fuzzy: 0232 whether the message is fuzzy 0233 0234 The state of fuzziness can be also checked and set by looking for 0235 and adding/removing the C{fuzzy} flag from the set of flags, 0236 but this is needed frequently enough to deserve a standalone attribute. 0237 Note: To "thoroughly" unfuzzy the message, see method L{unfuzzy}. 0238 @type fuzzy: bool 0239 0240 @ivar untranslated: (read-only) 0241 whether the message is untranslated (False for fuzzy messages) 0242 @type untranslated: bool 0243 0244 @ivar translated: (read-only) 0245 whether the message is translated (False for fuzzy messages) 0246 @type translated: bool 0247 0248 @ivar active: (read-only) 0249 whether the translation of the message is used at destination 0250 (C{False} for untranslated, fuzzy and obsolete messages) 0251 @type active: bool 0252 0253 @ivar format: (read-only) 0254 the format flag of the message (e.g. C{c-format}) or empty string 0255 @type format: string 0256 0257 @ivar refline: 0258 referent line number of the message inside the catalog 0259 0260 Valid only if there were no modifications to the catalog, otherwise 0261 undefined (made valid again after syncing the catalog). 0262 Normally this is the line number of C{msgid} keyword, 0263 but not guaranteed to be so. 0264 @type refline: int 0265 0266 @ivar refentry: 0267 referent entry number of the message inside the catalog 0268 0269 Valid only if there were no additions/removals of messages from the 0270 catalog, otherwise undefined (made valid again after syncing the 0271 catalog). 0272 @type refentry: int 0273 0274 @ivar key_previous: (read-only) previous key composition 0275 0276 Like L{key}, except this is for previous fields. 0277 If there are no previous fields, this is C{None}. 0278 0279 The value is an undefined serialization of C{msgctxt_previous} 0280 and C{msgid_previous}. 0281 @type key: string or C{None} 0282 0283 @see: L{Message} 0284 @see: L{MessageUnsafe} 0285 """ 0286 0287 def __init__ (self, getsetattr): 0288 """ 0289 Internal constructor for subclasses' usage. 0290 0291 @param getsetattr: 0292 the object with C{__getattr__} and C{__setattr__} methods, 0293 as handler for unhandled instance attributes 0294 """ 0295 0296 self.__dict__["^getsetattr"] = getsetattr 0297 self._colorize_prev = 0 0298 0299 0300 def __getattr__ (self, att): 0301 """ 0302 Attribute getter. 0303 0304 Processes read-only attributes, and sends others to the getter 0305 given by the constructor. 0306 0307 @param att: name of the attribute to get 0308 @returns: attribute value 0309 """ 0310 0311 if 0: pass 0312 0313 elif att == "translated": 0314 if self.fuzzy: 0315 return False 0316 # Consider message translated if at least one msgstr is translated: 0317 # that's how gettext tools do, but then they report an error for 0318 # missing argument in non-translated msgstrs. 0319 for val in self.msgstr: 0320 if val: 0321 return True 0322 return False 0323 0324 elif att == "untranslated": 0325 if self.fuzzy: 0326 return False 0327 for val in self.msgstr: 0328 if val: 0329 return False 0330 return True 0331 0332 elif att == "active": 0333 return self.translated and not self.obsolete 0334 0335 elif att == "key": 0336 return self._compose(["msgctxt", "msgid"]) 0337 0338 elif att == "fmt": 0339 return self._compose(["msgctxt", "msgid", 0340 "msgid_plural", "msgstr", 0341 "fuzzy", "obsolete"]) 0342 0343 elif att == "inv": 0344 return self._compose(["msgctxt", "msgid", 0345 "msgid_plural", "msgstr", 0346 "fuzzy", "obsolete", 0347 "manual_comment", "msgctxt_previous", 0348 "msgid_previous", "msgid_plural_previous"]) 0349 0350 elif att == "trn": 0351 return self._compose(["msgstr", "fuzzy", "manual_comment"]) 0352 0353 elif att == "format": 0354 format_flag = "" 0355 for flag in self.flag: 0356 if flag.find("-format") >= 0: 0357 format_flag = flag 0358 break 0359 return format_flag 0360 0361 elif att == "fuzzy": 0362 return "fuzzy" in self.flag 0363 0364 elif att == "key_previous": 0365 if self.msgid_previous is not None: 0366 return self._compose(["msgctxt_previous", "msgid_previous"]) 0367 else: 0368 return None 0369 0370 else: 0371 return self.__dict__["^getsetattr"].__getattr__(self, att) 0372 0373 0374 def _compose (self, fields): 0375 0376 fmtvals = [] 0377 for field in fields: 0378 val = self.get(field) 0379 if field in _Message_state_fields: 0380 fval = val and "1" or "0" 0381 elif field in _Message_list_fields: 0382 fval = "\x02".join(["%s" % x for x in val]) 0383 elif field in _Message_list2_fields: 0384 fval = "\x02".join(["%s:%s" % tuple(x) for x in val]) 0385 elif field in _Message_set_fields: 0386 vlst = ["%s" % x for x in val] 0387 vlst.sort() 0388 fval = "\x02".join(vlst) 0389 else: 0390 fval = val is None and "\x00" or "%s" % val 0391 fmtvals.append(fval) 0392 return "\x04".join(fmtvals) 0393 0394 0395 def get (self, att, default=None): 0396 """ 0397 Get attribute value. 0398 0399 Allows accessing the message like a dictionary. 0400 0401 @param att: name of the attribute to get 0402 @type att: string 0403 @param default: value to return if attribute does not exist 0404 0405 @returns: value of the attribute or the default value 0406 """ 0407 0408 if hasattr(self, att): 0409 return getattr(self, att) 0410 else: 0411 return default 0412 0413 0414 def __setattr__ (self, att, val): 0415 """ 0416 Attribute setter. 0417 0418 May act upon some attributes (e.g. checks), but finally passes 0419 all of them to the setter given by the constructor. 0420 0421 @param att: name of the attribute to set 0422 @param val: value to set the attribute to 0423 """ 0424 0425 if 0: pass 0426 0427 elif att == "fuzzy": 0428 if val == True: 0429 self.flag.add("fuzzy") 0430 elif "fuzzy" in self.flag: 0431 self.flag.remove("fuzzy") 0432 0433 else: 0434 self.__dict__["^getsetattr"].__setattr__(self, att, val) 0435 0436 0437 def __eq__ (self, omsg): 0438 """ 0439 Reports whether messages are equal in all apparent parts. 0440 0441 "Apparent" parts include all those which are visible in the PO file. 0442 I.e. the check will ignore internal states, like line caches, etc. 0443 0444 @returns: C{True} if messages are equal in apparent parts 0445 @rtype: bool 0446 """ 0447 0448 # Make messages the same type. 0449 # NOTE: All this instead of just omsg = type(self)(omsg) 0450 # for the sake of performance. 0451 if not isinstance(omsg, Message_base): 0452 omsg = MessageUnsafe(omsg) 0453 msg = self 0454 if isinstance(self, Message) and isinstance(omsg, MessageUnsafe): 0455 msg = MessageUnsafe(msg) 0456 elif isinstance(self, MessageUnsafe) and isinstance(omsg, Message): 0457 omsg = MessageUnsafe(omsg) 0458 0459 for field in _Message_all_fields: 0460 if msg.get(field) != omsg.get(field): 0461 return False 0462 0463 return True 0464 0465 0466 def __ne__ (self, omsg): 0467 """ 0468 Reports whether messages are not equal in some apparent parts. 0469 0470 Equivalent to C{not (self == omsg)}. 0471 0472 @returns: C{False} if messages are equal in all apparent parts 0473 @rtype: bool 0474 """ 0475 0476 return not self.__eq__(omsg) 0477 0478 def __hash__ (self): 0479 return id(self)//16 0480 0481 def _renew_lines_bymod (self, mod, wrapf=wrap_field, force=False, 0482 colorize=0): 0483 0484 prefix = {} 0485 if self.obsolete: 0486 prefix["curr"] = "#~ " 0487 prefix["prev"] = "#~| " 0488 else: 0489 prefix["curr"] = "" 0490 prefix["prev"] = "#| " 0491 0492 if force or mod["manual_comment"] or not self._lines_manual_comment: 0493 self._lines_manual_comment = [] 0494 for manc in self.manual_comment: 0495 ls = wrap_comment_unwrap("", manc) 0496 if colorize >= 2: 0497 ls = [ColorString("<grey>%s</grey>") % x for x in ls] 0498 self._lines_manual_comment.extend(ls) 0499 0500 if force or mod["auto_comment"] or not self._lines_auto_comment: 0501 self._lines_auto_comment = [] 0502 for autoc in self.auto_comment: 0503 ls = wrap_comment_unwrap(".", autoc) 0504 if colorize >= 2: 0505 ls = [ColorString("<blue>%s</blue>") % x for x in ls] 0506 self._lines_auto_comment.extend(ls) 0507 0508 if force or mod["source"] or not self._lines_source: 0509 self._lines_source = [] 0510 srcrefs = [] 0511 for src in self.source: 0512 if src[1] > 0: 0513 srcrefs.append(src[0] + ":" + str(src[1])) 0514 else: 0515 srcrefs.append(src[0]) 0516 if srcrefs: 0517 ls = wrap_comment(":", cjoin(srcrefs, " ")) 0518 if colorize >= 2: 0519 ls = [ColorString("<blue>%s</blue>") % x for x in ls] 0520 self._lines_source = ls 0521 0522 if force or mod["flag"] or not self._lines_flag: 0523 self._lines_flag = [] 0524 # Rearange so that fuzzy is first, if present. 0525 flst = [] 0526 for fl in self.flag: 0527 if fl == "fuzzy": 0528 if colorize >= 1: 0529 fl = ColorString("<underline>%s</underline>") % fl 0530 flst.insert(0, fl) 0531 else: 0532 flst.append(fl) 0533 if flst: 0534 ls = wrap_comment(",", cjoin(flst, ", ")) 0535 if colorize >= 2: 0536 ls = [ColorString("<blue>%s</blue>") % x for x in ls] 0537 self._lines_flag = ls 0538 0539 for att in _Message_single_fields: 0540 att_lins = "_lines_" + att 0541 if force or mod[att] or not self.__dict__[att_lins]: 0542 # modcount of this string > 0 or lines not cached or forced 0543 self.__dict__[att_lins] = [] 0544 msgsth = getattr(self, att) 0545 if msgsth is not None or att in _Message_mandatory_fields: 0546 if msgsth is None: 0547 msgsth = "" 0548 if att.endswith("_previous"): 0549 fname = att[:-len("_previous")] 0550 pstat = "prev" 0551 else: 0552 fname = att 0553 pstat = "curr" 0554 if colorize >= 1: 0555 fname = ColorString("<bold>%s</bold>") % fname 0556 self.__dict__[att_lins] = wrapf(fname, _escape(msgsth), 0557 prefix[pstat]) 0558 0559 # msgstr must be renewed if the plurality of the message changed. 0560 new_plurality = ( getattr(self, "_lines_msgstr", []) 0561 and ( ( self.msgid_plural is None 0562 and "msgstr[" in self._lines_msgstr[0]) 0563 or ( self.msgid_plural is not None 0564 and "msgstr[" not in self._lines_msgstr[0]))) 0565 0566 if force or mod["msgstr"] or not self._lines_msgstr or new_plurality: 0567 self._lines_msgstr = [] 0568 msgstr = self.msgstr or [""] 0569 if self.msgid_plural is None: 0570 fname = "msgstr" 0571 if colorize >= 1: 0572 fname = ColorString("<bold>%s</bold>") % fname 0573 self._lines_msgstr.extend(wrapf(fname, 0574 _escape(msgstr[0]), 0575 prefix["curr"])) 0576 else: 0577 for i in range(len(msgstr)): 0578 fname = "msgstr[%d]" % i 0579 if colorize >= 1: 0580 fname = ColorString("<bold>%s</bold>") % fname 0581 self._lines_msgstr.extend(wrapf(fname, 0582 _escape(msgstr[i]), 0583 prefix["curr"])) 0584 0585 # Marshal the lines into proper order. 0586 self._lines_all = [] 0587 lins = self._lines_all 0588 0589 lins.extend(self._lines_manual_comment) 0590 lins.extend(self._lines_auto_comment) 0591 if not self.obsolete: # no source for an obsolete message 0592 lins.extend(self._lines_source) 0593 lins.extend(self._lines_flag) 0594 0595 # Actually, it might make sense regardless... 0596 ## Old originals makes sense only for a message with a fuzzy flag. 0597 #if self.fuzzy: 0598 lins.extend(self._lines_msgctxt_previous) 0599 lins.extend(self._lines_msgid_previous) 0600 lins.extend(self._lines_msgid_plural_previous) 0601 0602 lins.extend(self._lines_msgctxt) 0603 lins.extend(self._lines_msgid) 0604 lins.extend(self._lines_msgid_plural) 0605 lins.extend(self._lines_msgstr) 0606 0607 if self._lines_all[-1] != "\n": 0608 lins.extend("\n") 0609 0610 0611 def to_lines (self, wrapf=wrap_field, force=False, colorize=0): 0612 """ 0613 The line-representation of the message. 0614 0615 Lines are returned with newlines included. 0616 0617 @param wrapf: 0618 the function used for wrapping message fields (msgctxt, msgid, ...) 0619 As arguments the function should accept the field name, 0620 the field text, and the prefix to all lines, 0621 and return the list of wrapped lines (with newlines included). 0622 @type wrapf: string, string, string -> list of strings 0623 0624 @param force: 0625 whether to force reformatting of all elements. 0626 Subclasses may keep a track of lines exactly as read from the 0627 PO file, and allow reformatting of only the modified elements of 0628 the message. 0629 @type force: bool 0630 0631 @param colorize: whether and how much to colorize the message. 0632 Typically useful when the message is output to terminal, 0633 HTML file, etc. as accompanying information to a user. 0634 If the value is 0, no colorization is applied; 0635 1 gives conservative colorization, 2 and more full colorization. 0636 @type colorize: int 0637 0638 @returns: formatted lines 0639 @rtype: list of strings 0640 0641 @see: L{pology.wrap} 0642 """ 0643 0644 # Renew lines if one of: forced, no lines formed yet, no modcounter, 0645 # different colorization. 0646 if colorize != self._colorize_prev: 0647 force = True 0648 if force or getattr(self, "modcount", True) or not self._lines_all: 0649 self._renew_lines(wrapf, force, colorize) 0650 self._colorize_prev = colorize 0651 0652 return self._lines_all 0653 0654 0655 def to_string (self, wrapf=wrap_field, force=False, colorize=0): 0656 """ 0657 The string-representation of the message. 0658 0659 Passes the arguments to L{to_lines} and joins the resulting list. 0660 0661 @see: L{to_lines} 0662 """ 0663 0664 return cjoin(self.to_lines(wrapf, force, colorize)) 0665 0666 0667 def _append_to_list (self, other, att): 0668 0669 self_list = getattr(self, att) 0670 other_list = getattr(other, att) 0671 for el in other_list: 0672 self_list.append(el) 0673 0674 0675 def _overwrite_list (self, other, att): 0676 0677 # Overwrites self list by element-assignment/append/pop, 0678 # so that modification history is tracked. 0679 self_list = getattr(self, att) 0680 other_list = getattr(other, att) 0681 self_len = len(self_list) 0682 other_len = len(other_list) 0683 if self_len <= other_len: 0684 for i in range(self_len): 0685 self_list[i] = other_list[i] 0686 for i in range(self_len, other_len): 0687 self_list.append(other_list[i]) 0688 else: 0689 for i in range(other_len): 0690 self_list[i] = other_list[i] 0691 for i in range(other_len, self_len): 0692 self_list.pop() 0693 0694 0695 def unfuzzy (self): 0696 """ 0697 Thoroughly unfuzzy the message. 0698 0699 Strictly speaking, a message is fuzzy if it has the C{fuzzy} flag set. 0700 Thus a message can be unfuzzied by removing this flag, either 0701 manually from the C{flag} set, or through attribute C{fuzzy}. 0702 But if there were previous fields (e.g. C{msgid_previous}) 0703 added to the message when it was made fuzzy on merge, they will 0704 remain in the message after it has been unfuzzied in this way. 0705 This is normally not wanted, and in such cases this method may 0706 be used to I{thouroughly} unfuzzy the message: remove C{fuzzy} flag, 0707 set C{fuzzy} attribute to C{False}, and all C{*_previous} 0708 attributes to C{None}. 0709 0710 If the message is not strictly fuzzy upon this call, 0711 it is undefined whether any present previous fields will be 0712 left untouched, or removed nontheless. 0713 0714 @returns: True if the message was unfuzzied, false otherwise 0715 """ 0716 0717 if not self.fuzzy: 0718 return False 0719 0720 self.fuzzy = False # also removes fuzzy flag 0721 self.msgctxt_previous = None 0722 self.msgid_previous = None 0723 self.msgid_plural_previous = None 0724 0725 return True 0726 0727 0728 def clear (self, keepmanc=False, msgstrlen=None): 0729 """ 0730 Revert message to pristine untranslated state. 0731 0732 Reverting to untranslated state removes manual comments (by default), 0733 C{fuzzy} flag, and previous fields, and clears C{msgstr} fields. 0734 0735 @param keepmanc: do not remove manual comments 0736 @type keepmanc: bool 0737 @param msgstrlen: the number of empty msgstr fields; 0738 if C{None}, the existing number of fields is preserved 0739 @type msgstrlen: int 0740 """ 0741 0742 if not keepmanc: 0743 self.manual_comment = type(self.manual_comment)() 0744 self.fuzzy = False # also removes fuzzy flag 0745 self.msgctxt_previous = None 0746 self.msgid_previous = None 0747 self.msgid_plural_previous = None 0748 if msgstrlen is None: 0749 msgstrlen = len(self.msgstr) 0750 self.msgstr = type(self.msgstr)([""] * msgstrlen) 0751 0752 0753 def state (self): 0754 """ 0755 Coded description of the translation state of the message. 0756 0757 Code string can be one of: 0758 "T" (translated), "F" (fuzzy), "U" (untranslated), 0759 "OT" (obsolete translated), "OF" (obsolete fuzzy), 0760 "OU" (obsolete untranslated). 0761 0762 @returns: coded translation state 0763 @rtype: string 0764 """ 0765 0766 if not self.obsolete: 0767 if self.fuzzy: 0768 return "F" 0769 elif self.translated: 0770 return "T" 0771 else: 0772 return "U" 0773 else: 0774 if self.fuzzy: 0775 return "OF" 0776 elif self.translated: 0777 return "OT" 0778 else: 0779 return "OU" 0780 0781 0782 def set (self, omsg): 0783 """ 0784 Copy all parts from the other message. 0785 0786 All mutable parts are deeply copied. 0787 0788 @param omsg: the message from which to copy the parts 0789 @type omsg: instance of L{Message_base} 0790 0791 @returns: self 0792 """ 0793 0794 return self._set_parts(omsg, _Message_all_fields) 0795 0796 0797 def set_key (self, omsg): 0798 """ 0799 Copy all key parts from the other message. 0800 0801 See L{key} attribute for the description and list of key parts. 0802 0803 All mutable parts are deeply copied. 0804 0805 @param omsg: the message from which to copy the parts 0806 @type omsg: instance of L{Message_base} 0807 0808 @returns: self 0809 """ 0810 0811 return self._set_parts(omsg, _Message_key_fields) 0812 0813 0814 def set_fmt (self, omsg): 0815 """ 0816 Copy all format parts from the other message. 0817 0818 See L{fmt} attribute for the description and list of format parts. 0819 0820 All mutable parts are deeply copied. 0821 0822 @param omsg: the message from which to copy the parts 0823 @type omsg: instance of L{Message_base} 0824 0825 @returns: self 0826 """ 0827 0828 return self._set_parts(omsg, _Message_fmt_fields) 0829 0830 0831 def set_inv (self, omsg): 0832 """ 0833 Copy extraction-invariant parts from the other message. 0834 0835 See L{inv} attribute for the description and list of 0836 extraction-invariant parts. 0837 0838 All mutable parts are deeply copied. 0839 0840 @param omsg: the message from which to copy the parts 0841 @type omsg: instance of L{Message_base} 0842 0843 @returns: self 0844 """ 0845 0846 return self._set_parts(omsg, _Message_inv_fields) 0847 0848 0849 def _set_parts (self, omsg, parts): 0850 """ 0851 Worker for set* methods. 0852 """ 0853 0854 for part in parts: 0855 oval = omsg.get(part) 0856 val = self.get(part) 0857 if oval is not None: 0858 if part in _Message_list2_fields: 0859 oval = type(val)([type(x)(x) for x in oval]) 0860 elif part in _Message_sequence_fields: 0861 oval = type(val)(oval) 0862 elif val is not None: 0863 oval = type(val)(oval) 0864 setattr(self, part, oval) 0865 0866 return self 0867 0868 0869 class Message (Message_base, Monitored): # order important for get/setattr 0870 """ 0871 The default class for catalog entries. 0872 0873 The interface is inherited from L{Message_base}, but when used through 0874 this class it behaves in a special way: the modifications are I{monitored}, 0875 such that no new attributes can be created by assignment 0876 and all assignments are checked for value types. 0877 If you don't need to modify the messages after creation, consider using 0878 the faster L{MessageUnsafe} class. 0879 0880 The loosely defined types in the base class (those with a star) 0881 are resolved into one of C{Mon*} types: L{Monlist}, L{Monset}, L{Monpair}. 0882 They implement some, but not all, of the functionality of their standard 0883 counterparts. 0884 0885 @see: L{Message_base} 0886 @see: L{MessageUnsafe} 0887 @see: L{pology.monitored} 0888 """ 0889 0890 def __init__ (self, init={}): 0891 """ 0892 Initializes the message elements by the values in the dictionary. 0893 0894 The dictionary keys are like the names of attributes in the 0895 interface, and not all must be supplied. Those left out will be 0896 initialized to appropriate null values. 0897 0898 The monitored sequences should be supplied as their ordinary 0899 counterparts (e.g. a C{list} in place of L{Monlist}), 0900 0901 @param init: dictionary of initial values 0902 @type init: dict 0903 """ 0904 0905 # NOTE: Make sure all sequences are shallow copied. 0906 0907 Message_base.__init__(self, Monitored) 0908 0909 self._manual_comment = Monlist(init.get("manual_comment", [])[:]) 0910 self._auto_comment = Monlist(init.get("auto_comment", [])[:]) 0911 self._source = Monlist(list(map(Monpair, init.get("source", [])[:]))) 0912 self._flag = Monset(init.get("flag", [])) 0913 0914 self._obsolete = init.get("obsolete", False) 0915 0916 self._msgctxt_previous = init.get("msgctxt_previous", None) 0917 self._msgid_previous = init.get("msgid_previous", None) 0918 self._msgid_plural_previous = init.get("msgid_plural_previous", None) 0919 0920 self._msgctxt = init.get("msgctxt", None) 0921 self._msgid = init.get("msgid", "") 0922 self._msgid_plural = init.get("msgid_plural", None) 0923 self._msgstr = Monlist(init.get("msgstr", [])[:]) 0924 0925 self._fuzzy = ("fuzzy" in self._flag and not self._obsolete) 0926 0927 self._refline = init.get("refline", -1) 0928 self._refentry = init.get("refentry", -1) 0929 0930 self.assert_spec_init(_Message_spec) 0931 0932 # Line caches. 0933 self._lines_all = init.get("_lines_all", [])[:] 0934 self._lines_manual_comment = init.get("_lines_manual_comment", [])[:] 0935 self._lines_auto_comment = init.get("_lines_auto_comment", [])[:] 0936 self._lines_source = init.get("_lines_source", [])[:] 0937 self._lines_flag = init.get("_lines_flag", [])[:] 0938 self._lines_msgctxt_previous = init.get("_lines_msgctxt_previous", [])[:] 0939 self._lines_msgid_previous = init.get("_lines_msgid_previous", [])[:] 0940 self._lines_msgid_plural_previous = init.get("_lines_msgid_plural_previous", [])[:] 0941 self._lines_msgctxt = init.get("_lines_msgctxt", [])[:] 0942 self._lines_msgid = init.get("_lines_msgid", [])[:] 0943 self._lines_msgid_plural = init.get("_lines_msgid_plural", [])[:] 0944 self._lines_msgstr = init.get("_lines_msgstr", [])[:] 0945 0946 def _renew_lines (self, wrapf=wrap_field, force=False, colorize=0): 0947 0948 if not self.obsolete_modcount: 0949 mod = {} 0950 mod["manual_comment"] = ( self.manual_comment_modcount 0951 or self.manual_comment.modcount) 0952 mod["auto_comment"] = ( self.auto_comment_modcount 0953 or self.auto_comment.modcount) 0954 mod["source"] = self.source_modcount or self.source.modcount 0955 mod["flag"] = self.flag_modcount or self.flag.modcount 0956 for att in _Message_single_fields: 0957 mod[att] = getattr(self, att + "_modcount") > 0 0958 mod["msgstr"] = self.msgstr_modcount or self.msgstr.modcount 0959 else: 0960 # Must recompute all lines if the message has been modified 0961 # by changing the obsolete status. 0962 mod = None 0963 force = True 0964 0965 return self._renew_lines_bymod(mod, wrapf, force, colorize) 0966 0967 0968 class MessageUnsafe (Message_base): 0969 """ 0970 The lightweight class for catalog entries, for read-only applications. 0971 0972 Unlike the L{Message}, this class does nothing special with attributes. 0973 The interface attributes are implemented as in L{Message_base}, 0974 where the starred lists are standard lists, starred sets 0975 standard sets, etc. There is no assignment and type checking, nor 0976 modification monitoring. You should use this class when messages are not 0977 expected to be modified, for the performance benefit. 0978 0979 The top modification counter still exists, but only as an ordinary 0980 inactive attribute, which the client code can manually increase 0981 to signal that the message has changed. This may be necessary for some 0982 client code, which relies on top counter to function properly. 0983 0984 @see: L{Message_base} 0985 """ 0986 0987 def __init__ (self, init={}): 0988 """ 0989 Initializes the message elements by the values in the dictionary. 0990 0991 The dictionary keys are like the names of attributes in the 0992 interface, and not all must be supplied. Those left out will be 0993 initialized to appropriate null values. 0994 0995 @param init: dictionary of initial values 0996 @type init: dict 0997 """ 0998 0999 # NOTE: Make sure all sequences are shallow copied. 1000 1001 Message_base.__init__(self, object) 1002 1003 self.manual_comment = list(init.get("manual_comment", [])) 1004 self.auto_comment = list(init.get("auto_comment", [])) 1005 self.source = [tuple(x) for x in init.get("source", [])] 1006 self.flag = set(init.get("flag", [])) 1007 1008 self.obsolete = init.get("obsolete", False) 1009 1010 self.msgctxt_previous = init.get("msgctxt_previous", None) 1011 self.msgid_previous = init.get("msgid_previous", None) 1012 self.msgid_plural_previous = init.get("msgid_plural_previous", None) 1013 1014 self.msgctxt = init.get("msgctxt", None) 1015 self.msgid = init.get("msgid", "") 1016 self.msgid_plural = init.get("msgid_plural", None) 1017 self.msgstr = list(init.get("msgstr", [""])) 1018 1019 self.refline = init.get("refline", -1) 1020 self.refentry = init.get("refentry", -1) 1021 1022 # No need to look for line caches, as lines must always be reformatted. 1023 1024 1025 def _renew_lines (self, wrapf=wrap_field, force=False, colorize=0): 1026 1027 # No monitoring, content must always be reformatted. 1028 return self._renew_lines_bymod(None, wrapf, True, colorize) 1029