Index: trunk/l10n-support/pology/scripts/poepatch.py =================================================================== --- trunk/l10n-support/pology/scripts/poepatch.py (revision 1515442) +++ trunk/l10n-support/pology/scripts/poepatch.py (revision 1515443) @@ -1,824 +1,824 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- """ Patch PO files from an embedded diff. Documented in C{doc/user/diffpatch.docbook#sec-dpdiff}. @author: Chusslove Illich (Часлав Илић) @license: GPLv3 """ try: import fallback_import_paths except: pass import sys import os import locale import re from tempfile import NamedTemporaryFile from pology import version, _, n_ from pology.colors import ColorOptionParser from pology.report import error, warning, report from pology.msgreport import error_on_msg, warning_on_msg import pology.config as pology_config from pology.fsops import str_to_unicode, mkdirpath, collect_catalogs from pology.fsops import exit_on_exception from pology.catalog import Catalog from pology.message import Message, MessageUnsafe from pology.header import Header from pology.diff import msg_ediff, msg_ediff_to_new, msg_ediff_to_old from pology.internal.poediffpatch import MPC, EDST from pology.internal.poediffpatch import msg_eq_fields, msg_copy_fields from pology.internal.poediffpatch import msg_clear_prev_fields from pology.internal.poediffpatch import diff_cats from pology.internal.poediffpatch import init_ediff_header from pology.internal.poediffpatch import get_msgctxt_for_headers _flag_ediff = u"ediff" _flag_ediff_to_cur = u"%s-to-cur" % _flag_ediff _flag_ediff_to_new = u"%s-to-new" % _flag_ediff _flag_ediff_no_match = u"%s-no-match" % _flag_ediff _flags_all = ( _flag_ediff, _flag_ediff_to_cur, _flag_ediff_to_new, _flag_ediff_no_match, ) def main (): locale.setlocale(locale.LC_ALL, "") # Get defaults for command line options from global config. cfgsec = pology_config.section("poepatch") def_do_merge = cfgsec.boolean("merge", True) # Setup options and parse the command line. usage = _("@info command usage", "%(cmd)s [OPTIONS] [OPTIONS] < EDIFF\n" "%(cmd)s -u [OPTIONS] PATHS...", cmd="%prog") desc = _("@info command description", "Apply embedded diff of PO files as patch.") ver = _("@info command version", u"%(cmd)s (Pology) %(version)s\n" u"Copyright © 2009, 2010 " u"Chusslove Illich (Часлав Илић) <%(email)s>", cmd="%prog", version=version(), email="caslav.ilic@gmx.net") opars = ColorOptionParser(usage=usage, description=desc, version=ver) opars.add_option( "-a", "--aggressive", action="store_true", dest="aggressive", default=False, help=_("@info command line option description", "Apply every message to its paired message in the target file, " "irrespective of whether its non-pairing parts match too.")) opars.add_option( "-d", "--directory", metavar=_("@info command line value placeholder", "DIR"), dest="directory", help=_("@info command line option description", "Prepend this directory path to any resolved target file path.")) opars.add_option( "-e", "--embed", action="store_true", dest="embed", default=False, help=_("@info command line option description", "Instead of applying resolved newer version of the message, " "add the full embedded diff into the target file.")) opars.add_option( "-i", "--input", metavar=_("@info command line value placeholder", "FILE"), dest="input", help=_("@info command line option description", "Read the patch from the given file instead of standard input.")) opars.add_option( "-n", "--no-merge", action="store_false", dest="do_merge", default=def_do_merge, help=_("@info command line option description", "Do not try to indirectly pair messages by merging catalogs.")) opars.add_option( "-p", "--strip", metavar=_("@info command line value placeholder", "NUM"), dest="strip", help=_("@info command line option description", "Strip the smallest prefix containing NUM leading slashes from " "each file name found in the ediff file (like in patch(1)). " "If not given, only the base name of each file is taken.")) opars.add_option( "-u", "--unembed", action="store_true", dest="unembed", default=False, help=_("@info command line option description", "Instead of applying a patch, resolve all embedded differences " "in given paths to newer versions of messages.")) (op, free_args) = opars.parse_args(str_to_unicode(sys.argv[1:])) # Could use some speedup. try: import psyco psyco.full() except ImportError: pass if not op.unembed: if free_args: error(_("@info", "Too many arguments in command line: %(argspec)s", argspec=" ".join(free_args))) if op.strip and not op.strip.isdigit(): error(_("@info", "Option %(opt)s expects a positive integer value.", opt="--strip")) apply_ediff(op) else: paths = [] for path in free_args: if not os.path.exists(path): warning(_("@info", "Path '%(path)s' does not exist.", path=path)) if os.path.isdir(path): paths.extend(collect_catalogs(path)) else: paths.append(path) for path in paths: unembed_ediff(path) def apply_ediff (op): # Read the ediff PO. dummy_stream_path = "" if op.input: if not os.path.isfile(op.input): error(_("@info", "Path '%(path)s' is not a file or does not exist.", path=op.input)) edfpath = op.input readfh = None else: edfpath = dummy_stream_path readfh = sys.stdin try: ecat = Catalog(edfpath, monitored=False, readfh=readfh) except: error(_("@info ediff is shorthand for \"embedded difference\"", "Error reading ediff '%(file)s'.", file=edfpath)) # Split ediff by diffed catalog into original and new file paths, # header message, and ordinary messages. hmsgctxt = ecat.header.get_field_value(EDST.hmsgctxt_field) if hmsgctxt is None: error(_("@info", "Header field '%(field)s' is missing in the ediff.", field=EDST.hmsgctxt_field)) edsplits = [] cehmsg = None smsgid = u"\x00" ecat.add_last(MessageUnsafe(dict(msgctxt=hmsgctxt, msgid=smsgid))) # sentry for emsg in ecat: if emsg.msgctxt == hmsgctxt: if cehmsg: # Record previous section. edsplits.append((fpaths, cehmsg, cemsgs)) if emsg.msgid == smsgid: # end sentry, avoid parsing below break # Mine original and new file paths out of header. fpaths = [] for fpath in emsg.msgid.split("\n")[:2]: # Strip leading "+ "/"- " fpath = fpath[2:] # Convert to planform path separators. fpath = re.sub(r"/+", os.path.sep, fpath) # Remove revision indicator. p = fpath.find(EDST.filerev_sep) if p >= 0: fpath = fpath[:p] # Strip path and append directory as requested. if op.strip: preflen = int(op.strip) lst = fpath.split(os.path.sep, preflen) if preflen + 1 == len(lst): fpath = lst[preflen] else: fpath = os.path.basename(fpath) else: fpath = os.path.basename(fpath) if op.directory and fpath: fpath = os.path.join(op.directory, fpath) # All done. fpaths.append(fpath) cehmsg = emsg cemsgs = [] else: cemsgs.append(emsg) # Prepare catalog for rejects and merges. rcat = Catalog("", create=True, monitored=False, wrapping=ecat.wrapping()) init_ediff_header(rcat.header, hmsgctxt=hmsgctxt, extitle="rejects") # Apply diff to catalogs. for fpaths, ehmsg, emsgs in edsplits: # Open catalog for patching. fpath1, fpath2 = fpaths if fpath1: # Diff from an existing catalog, open it. if not os.path.isfile(fpath1): warning(_("@info", "Path '%(path)s' is not a file or does not exist, " "skipping it.", path=fpath1)) continue try: cat = Catalog(fpath1) except: warning(_("@info", "Error reading catalog '%(file)s', skipping it.", file=fpath1)) continue elif fpath2: # New catalog added in diff, create it (or open if it exists). try: mkdirpath(os.path.dirname(fpath2)) cat = Catalog(fpath2, create=True) if cat.created(): cat.set_wrapping(ecat.wrapping()) except: if os.path.isfile(fpath2): warning(_("@info", "Error reading catalog '%(file)s', skipping it.", file=fpath1)) else: warning(_("@info", "Cannot create catalog '%(file)s', skipping it.", file=fpath2)) continue else: error(_("@info", "Both catalogs in ediff indicated not to exist.")) # Do not try to patch catalog with embedded differences # (i.e. previously patched using -e). if cat.header.get_field_value(EDST.hmsgctxt_field) is not None: warning(_("@info", "Catalog '%(file)s' already contains " "embedded differences, skipping it.", file=cat.filename)) continue # Do not try to patch catalog if the patch contains # unresolved split differences. if reduce(lambda r, x: r or _flag_ediff_to_new in x.flag, emsgs, False): warning(_("@info", "Patch for catalog '%(file)s' contains unresolved " "split differences, skipping it.", file=cat.filename)) continue # Patch the catalog. rejected_ehmsg = patch_header(cat, ehmsg, ecat, op) rejected_emsgs_flags = patch_messages(cat, emsgs, ecat, op) any_rejected = rejected_ehmsg or rejected_emsgs_flags if fpath2 or any_rejected: created = cat.created() if cat.sync(): if not created: if any_rejected and op.embed: report(_("@info:progress E is for \"with embedding\"", "Partially patched (E): %(file)s", file=cat.filename)) elif any_rejected: report(_("@info:progress", "Partially patched: %(file)s", file=cat.filename)) elif op.embed: report(_("@info:progress E is for \"with embedding\"", "Patched (E): %(file)s", file=cat.filename)) else: report(_("@info:progress", "Patched: %(file)s", file=cat.filename)) else: if op.embed: report(_("@info:progress E is for \"with embedding\"", "Created (E): %(file)s", file=cat.filename)) else: report(_("@info:progress", "Created: %(file)s", file=cat.filename)) else: pass #report("unchanged: %s" % cat.filename) else: os.unlink(fpath1) report(_("@info:progress", "Removed: %(file)s", file=fpath1)) # If there were any rejects and reembedding is not in effect, # record the necessary to present them. if any_rejected and not op.embed: if not rejected_ehmsg: # Clean header diff. ehmsg.manual_comment = ehmsg.manual_comment[:1] ehmsg.msgstr[0] = u"" rcat.add_last(ehmsg) for emsg, flag in rejected_emsgs_flags: # Reembed to avoid any conflicts. msg1, msg2, msg1_s, msg2_s = resolve_diff_pair(emsg) emsg = msg_ediff(msg1_s, msg2_s, emsg=msg2_s, ecat=rcat, enoctxt=hmsgctxt) if flag: emsg.flag.add(flag) rcat.add_last(emsg) # If there were any rejects, write them out. if len(rcat) > 0: # Construct paths for embedded diffs of rejects. rsuff = "rej" if ecat.filename != dummy_stream_path: rpath = ecat.filename p = rpath.rfind(".") if p < 0: p = len(rpath) rpath = rpath[:p] + (".%s" % rsuff) + rpath[p:] else: rpath = "stdin.%s.po" % rsuff rcat.filename = rpath rcat.sync(force=True, noobsend=True) report(_("@info:progress file to which rejected parts of the patch " "have been written to", "*** Rejects: %(file)s", file=rcat.filename)) # Patch application types. _pt_merge, _pt_insert, _pt_remove = range(3) def patch_messages (cat, emsgs, ecat, options): # It may happen that a single message from original catalog # is paired with more than one from the diff # (e.g. single old translated message going into two new fuzzy). # Therefore paired messages must be tracked, to know if patched # message can be merged into the existing, or it must be inserted. pmsgkeys = set() # Triplets for splitting directly unapplicable patches into two. # Delay building of triplets until needed for the first time. striplets_pack = [None] def striplets (): if striplets_pack[0] is None: striplets_pack[0] = build_splitting_triplets(emsgs, cat, options) return striplets_pack[0] # Check whether diffs apply, and where and how if they do. rejected_emsgs_flags = [] patch_specs = [] for emsg in emsgs: pspecs = msg_apply_diff(cat, emsg, ecat, pmsgkeys, striplets) for pspec in pspecs: emsg_m, flag = pspec[:2] if flag == _flag_ediff or options.embed: patch_specs.append(pspec) if flag != _flag_ediff: rejected_emsgs_flags.append((emsg_m, flag)) # Sort accepted patches by position of application. patch_specs.sort(key=lambda x: x[3]) # Add accepted patches to catalog. incpos = 0 for emsg, flag, typ, pos, msg1, msg2, msg1_s, msg2_s in patch_specs: if pos is not None: pos += incpos if options.embed: # Embedded diff may conflict one of the messages in catalog. # Make a new diff of special messages, # and embed them either into existing message in catalog, # or into new message. if typ == _pt_merge: tmsg = cat[pos] tpos = pos else: tmsg = MessageUnsafe(msg2 or {}) tpos = None emsg = msg_ediff(msg1_s, msg2_s, emsg=tmsg, ecat=cat, eokpos=tpos) if 0:pass elif typ == _pt_merge: if not options.embed: cat[pos].set_inv(msg2) else: cat[pos].flag.add(flag) elif typ == _pt_insert: if not options.embed: cat.add(Message(msg2), pos) else: cat.add(Message(emsg), pos) cat[pos].flag.add(flag) incpos += 1 elif typ == _pt_remove: if pos is None: continue if not options.embed: cat.remove(pos) incpos -= 1 else: cat[pos].flag.add(flag) else: error_on_msg(_("@info", "Unknown patch type %(type)s.", type=typ), emsg, ecat) return rejected_emsgs_flags def msg_apply_diff (cat, emsg, ecat, pmsgkeys, striplets): msg1, msg2, msg1_s, msg2_s = resolve_diff_pair(emsg) # Try to select existing message from the original messages. # Order is important, should try first new, then old # (e.g. if an old fuzzy was resolved to new after diff was made). msg = None if msg2 and msg2 in cat: msg = cat[msg2] elif msg1 and msg1 in cat: msg = cat[msg1] patch_specs = [] # Try to apply the patch. if msg_patchable(msg, msg1, msg2): # Patch can be directly applied. if msg1 and msg2: if msg.key not in pmsgkeys: typ = _pt_merge pos = cat.find(msg) pmsgkeys.add(msg.key) else: typ = _pt_insert pos, weight = cat.insertion_inquiry(msg2) elif msg2: # patch adds a message if msg: typ = _pt_merge pos = cat.find(msg) pmsgkeys.add(msg.key) else: typ = _pt_insert pos, weight = cat.insertion_inquiry(msg2) elif msg1: # patch removes a message if msg: typ = _pt_remove pos = cat.find(msg) pmsgkeys.add(msg.key) else: typ = _pt_remove pos = None # no position to remove from else: # Cannot happen. error_on_msg(_("@info", "Neither the old nor the new message " "in the diff is indicated to exist."), emsg, ecat) patch_specs.append((emsg, _flag_ediff, typ, pos, msg1, msg2, msg1_s, msg2_s)) else: # Patch cannot be applied directly, # try to split into old-to-current and current-to-new diffs. split_found = False if callable(striplets): striplets = striplets() # delayed creation of splitting triplets for i in range(len(striplets)): m1_t, m1_ts, m2_t, m2_ts, m_t, m_ts1, m_ts2 = striplets[i] if msg1.inv == m1_t.inv and msg2.inv == m2_t.inv: striplets.pop(i) # remove to not slow further searches split_found = True break if split_found: # Construct new corresponding diffs. em_1c = msg_ediff(m1_ts, m_ts1, emsg=MessageUnsafe(m_t)) em_c2 = msg_ediff(m_ts2, m2_ts, emsg=MessageUnsafe(m2_t)) # Current-to-new can be merged or inserted, # and old-to-current is then inserted just before it. if m_t.key not in pmsgkeys: typ = _pt_merge pos = cat.find(m_t) pmsgkeys.add(m_t.key) else: typ = _pt_insert pos, weight = cat.insertion_inquiry(m2_t) # Order of adding patch specs here important for rejects file. patch_specs.append((em_1c, _flag_ediff_to_cur, _pt_insert, pos, m1_t, m_t, m1_ts, m_ts1)) patch_specs.append((em_c2, _flag_ediff_to_new, typ, pos, m_t, m2_t, m_ts2, m2_ts)) # The patch is totally rejected. # Will be inserted if reembedding requested, so compute insertion. if not patch_specs: typ = _pt_insert if msg2 is not None: pos, weight = cat.insertion_inquiry(msg2) else: pos = len(cat) patch_specs.append((emsg, _flag_ediff_no_match, typ, pos, msg1, msg2, msg1_s, msg2_s)) return patch_specs def msg_patchable (msg, msg1, msg2): # Check for cases where current message does not match old or new, # but there is a transformation that can also be cleanly merged. msg_m = msg if 0: pass # Old and new are translated, but current is fuzzy and has previous fields. # Transform current to its previous state, from which it may have became # fuzzy by merging with templates. elif ( msg and msg.fuzzy and msg.key_previous is not None and msg1 and not msg1.fuzzy and msg2 and not msg2.fuzzy ): msg_m = MessageUnsafe(msg) msg_copy_fields(msg, msg_m, MPC.prevcurr_fields) msg_clear_prev_fields(msg_m) msg_m.fuzzy = False # Old is None, new is translated, and current is untranslated. # Add translation of new to current, since it may have been added as # untranslated after merging with templates. elif msg and msg.untranslated and not msg1 and msg2 and msg2.translated: msg_m = MessageUnsafe(msg) msg_copy_fields(msg2, msg_m, ["msgstr"]) if msg1 and msg2: return msg and msg_m.inv in (msg1.inv, msg2.inv) elif msg2: return not msg or msg_m.inv == msg2.inv elif msg1: return not msg or msg_m.inv == msg1.inv else: return not msg def resolve_diff_pair (emsg): # Recover old and new message according to diff. # Resolve into copies of ediff message, to preserve non-inv parts. emsg1 = MessageUnsafe(emsg) msg1_s = msg_ediff_to_old(emsg1, rmsg=emsg1) emsg2 = MessageUnsafe(emsg) msg2_s = msg_ediff_to_new(emsg2, rmsg=emsg2) # Resolve any special pairings. msg1, msg2 = msg1_s, msg2_s if not msg1_s or not msg2_s: # No special cases if either message non-existant. pass # Cases f-nf-*. elif msg1_s.fuzzy and not msg2_s.fuzzy: # Case f-nf-ecc. if ( msg2_s.key_previous is None and not msg_eq_fields(msg1_s, msg2_s, MPC.curr_fields) ): msg1 = MessageUnsafe(msg1_s) msg_copy_fields(msg1_s, msg1, MPC.currprev_fields) msg_copy_fields(msg2_s, msg1, MPC.curr_fields) # Case f-nf-necc. elif msg2_s.key_previous is not None: msg1 = MessageUnsafe(msg1_s) msg2 = MessageUnsafe(msg2_s) msg_copy_fields(msg2_s, msg1, MPC.prevcurr_fields) msg_clear_prev_fields(msg2) # Cases nf-f-*. elif not msg1_s.fuzzy and msg2_s.fuzzy: # Case nf-f-ecp. if ( msg1_s.key_previous is None and not msg_eq_fields(msg1_s, msg2_s, MPC.curr_fields) ): msg2 = MessageUnsafe(msg2_s) msg_copy_fields(msg1_s, msg2, MPC.currprev_fields) # Case nf-f-necp. elif msg1_s.key_previous is not None: msg1 = MessageUnsafe(msg1_s) msg2 = MessageUnsafe(msg2_s) msg_copy_fields(msg1_s, msg2, MPC.prev_fields) msg_clear_prev_fields(msg1) return msg1, msg2, msg1_s, msg2_s def build_splitting_triplets (emsgs, cat, options): # Create catalogs of old and new messages. cat1 = Catalog("", create=True, monitored=False) cat2 = Catalog("", create=True, monitored=False) for emsg in emsgs: msg1, msg2, msg1_s, msg2_s = resolve_diff_pair(emsg) if msg1: cat1.add_last(msg1) if msg2: cat2.add_last(msg2) # Make headers same, to avoid any diffs there. cat1.header = cat.header cat2.header = cat.header # Write created catalogs to disk if # msgmerge may be used on files during diffing. if options.do_merge: tmpfs = [] # to avoid garbage collection until the function returns for tcat, tsuff in ((cat1, "1"), (cat2, "2")): tmpf = NamedTemporaryFile(prefix="poepatch-split-%s-" % tsuff, suffix=".po") tmpfs.append(tmpf) tcat.filename = tmpf.name tcat.sync(force=True) # Create the old-to-current and current-to-new diffs. ecat_1c = Catalog("", create=True, monitored=False) diff_cats(cat1, cat, ecat_1c, options.do_merge, wadd=False, wrem=False) ecat_c2 = Catalog("", create=True, monitored=False) diff_cats(cat, cat2, ecat_c2, options.do_merge, wadd=False, wrem=False) # Mine splitting triplets out of diffs. sdoublets_1c = {} for emsg in ecat_1c: m1_t, m_t, m1_ts, m_ts1 = resolve_diff_pair(emsg) sdoublets_1c[m_t.key] = [m1_t, m1_ts, m_t, m_ts1] sdoublets_c2 = {} for emsg in ecat_c2: m_t, m2_t, m_ts2, m2_ts = resolve_diff_pair(emsg) sdoublets_c2[m_t.key] = [m_t, m_ts2, m2_t, m2_ts] common_keys = set(sdoublets_1c).intersection(sdoublets_c2) striplets = [] for key in common_keys: m1_t, m1_ts, m_t, m_ts1 = sdoublets_1c[key] m_t, m_ts2, m2_t, m2_ts = sdoublets_c2[key] striplets.append((m1_t, m1_ts, m2_t, m2_ts, m_t, m_ts1, m_ts2)) return striplets def patch_header (cat, ehmsg, ecat, options): if not ehmsg.msgstr[0]: # no header diff, only metadata return None ehmsg_clean = clear_header_metadata(ehmsg) # Create reduced headers. hmsg1 = msg_ediff_to_old(ehmsg_clean) hmsg2 = msg_ediff_to_new(ehmsg_clean) hmsg = not cat.created() and cat.header.to_msg() or None hdrs = [] for m in (hmsg, hmsg1, hmsg2): h = m is not None and reduce_header_fields(Header(m)) or None hdrs.append(h) rhdr, rhdr1, rhdr2 = hdrs # Decide if the header can be cleanly patched. clean = False if not rhdr: clean = rhdr1 or rhdr2 else: clean = (rhdr1 and rhdr == rhdr1) or (rhdr2 and rhdr == rhdr2) if clean: if not options.embed: if hmsg2: cat.header = Header(hmsg2) else: # Catalog will be removed if no messages are rejected, # and otherwise the header should stay as-is. pass else: if cat.created(): cat.header = Header(hmsg2) ehmsg = MessageUnsafe(ehmsg) ehmsg.flag.add(_flag_ediff) hmsgctxt = get_msgctxt_for_headers(cat) ehmsg.msgctxt = hmsgctxt cat.header.set_field(EDST.hmsgctxt_field, hmsgctxt) cat.add(Message(ehmsg), 0) return None else: return ehmsg # Clear header diff message of metadata. # A copy of the message is returned. def clear_header_metadata (ehmsg): ehmsg = MessageUnsafe(ehmsg) ehmsg.manual_comment.pop(0) ehmsg.msgctxt = None ehmsg.msgid = u"" return ehmsg # Remove known unimportant fields from the header, # to ignore them on comparisons. def reduce_header_fields (hdr): rhdr = Header(hdr) for field in ( "POT-Creation-Date", "PO-Revision-Date", "Last-Translator", "X-Generator", ): rhdr.remove_field(field) return rhdr def unembed_ediff (path, all=False, old=False): try: cat = Catalog(path) except: warning(_("@info", "Error reading catalog '%(file)s', skipping it.", file=path)) return hmsgctxt = cat.header.get_field_value(EDST.hmsgctxt_field) if hmsgctxt is not None: cat.header.remove_field(EDST.hmsgctxt_field) uehmsg = None unembedded = {} for msg in cat: ediff_flag = None for flag in _flags_all: if flag in msg.flag: ediff_flag = flag msg.flag.remove(flag) if not ediff_flag and not all: continue if ediff_flag in (_flag_ediff_no_match, _flag_ediff_to_new): # Throw away fully rejected embeddings, i.e. reject the patch. # For split-difference embeddings, throw away the current-to-new; # this effectively rejects the patch, which is safest thing to do. cat.remove_on_sync(msg) elif hmsgctxt is not None and msg.msgctxt == hmsgctxt: if uehmsg: warning_on_msg(_("@info", "Unembedding results in duplicate header, " "previous header at %(line)d(#%(entry)d); " "skipping it.", line=uehmsg.refline, entry=uehmsg.refentry), msg, cat) return msg_ediff_to_x = not old and msg_ediff_to_new or msg_ediff_to_old hmsg = msg_ediff_to_x(clear_header_metadata(msg)) if hmsg.msgstr and hmsg.msgstr[0]: cat.header = Header(hmsg) cat.remove_on_sync(msg) uehmsg = msg else: msg1, msg2, msg1_s, msg2_s = resolve_diff_pair(msg) tmsg = (not old and (msg2,) or (msg1,))[0] if tmsg is not None: if tmsg.key in unembedded: msg_p = unembedded[tmsg.key] warning_on_msg(_("@info", "Unembedding results in " "duplicate message, previous message " "at %(line)d(#%(entry)d); skipping it.", line=msg_p.refline, entry=msg_p.refentry), msg, cat) return - msg.set(msg2) + msg.set(Message(msg2)) unembedded[tmsg.key] = msg else: cat.remove_on_sync(msg) if cat.sync(): report(_("@info:progress", "Unembedded: %(file)s", file=cat.filename)) if __name__ == '__main__': exit_on_exception(main)