def diff_hdrs(hdr1, hdr2, vpath1, vpath2, hmsgctxt, ecat, colorize): hmsg1, hmsg2 = [ x and MessageUnsafe(x.to_msg()) or None for x in (hdr1, hdr2) ] ehmsg = hmsg2 and MessageUnsafe(hmsg2) or None ehmsg, dr = msg_ediff(hmsg1, hmsg2, emsg=ehmsg, ecat=ecat, colorize=colorize, diffr=True) if dr == 0.0: # Revert to empty message if no difference between headers. ehmsg = MessageUnsafe() # Add visual paths as old/new segments into msgid. vpaths = [vpath1, vpath2] # Always use slashes as path separator, for portability of ediffs. vpaths = [x.replace(os.path.sep, "/") for x in vpaths] ehmsg.msgid = u"- %s\n+ %s" % tuple(vpaths) # Add trailing newline if msgstr has it, again to appease msgfmt. if ehmsg.msgstr[0].endswith("\n"): ehmsg.msgid += "\n" # Add context identifying the diffed message as header. ehmsg.msgctxt = hmsgctxt # Add conspicuous separator at the top of the header. ehmsg.manual_comment.insert(0, u"=" * 76) return ehmsg, dr > 0.0
def _add_msg_diff(msg1, msg2, ecat, colorize, fnsyn=None): # Skip diffing if old and new messages are "same". if msg1 and msg2 and msg1.inv == msg2.inv: return 0 # Create messages for special pairings. msg1_s, msg2_s = _create_special_diff_pair(msg1, msg2) # Create the diff. tmsg = msg2 or msg1 emsg = msg2_s or msg1_s if emsg is tmsg: emsg = MessageUnsafe(tmsg) emsg = msg_ediff(msg1_s, msg2_s, emsg=emsg, ecat=ecat, colorize=colorize) # Add to the diff catalog. if fnsyn is None: ecat.add(emsg, len(ecat)) else: ecat.add(emsg, srefsyn=fnsyn) return 1
def hybdl(path, path0, accnohyb=False): cat = Catalog(path) cat0 = Catalog(path0, monitored=False) nhybridized = 0 nstopped = 0 for msg in cat: if "no-hybdl" in manc_parse_flag_list(msg, "|"): continue # Unembed diff if message was diffed for review. # Replace ediff with manual review flag. diffed = False for flag in msg.flag: if flag.startswith("ediff"): msg.flag.remove(flag) diffed = True if diffed: msg_ediff_to_new(msg, msg) msg.flag.add(u"reviewed") # Fetch original message. msg0 = cat0.get(msg) if msg0 is None: warning_on_msg( _("@info", "Message does not exist in the original catalog."), msg, cat) nstopped += 1 continue if len(msg.msgstr) != len(msg0.msgstr): warning_on_msg( _( "@info", "Number of translations not same as in " "the original message."), msg, cat) nstopped += 1 continue if msg.msgstr == msg0.msgstr: # No changes, nothing new to hybridize. continue # Hybridize translation. textsh = [] textshinv = [] for text0, text in zip(msg0.msgstr, msg.msgstr): texth = tohi(text0, text, parthyb=True) textsh.append(texth) if not accnohyb: texthinv = tohi(text, text0, parthyb=True) textshinv.append(texthinv) if accnohyb or textsh == textshinv: for i, texth in zip(range(len(msg.msgstr)), textsh): msg.msgstr[i] = texth nhybridized += 1 else: nstopped += 1 msgh = MessageUnsafe(msg) msgh.msgstr = textsh msghinv = MessageUnsafe(msg) msghinv.msgstr = textshinv msg_ediff(msghinv, msgh, emsg=msgh, colorize=True) report_msg_content(msgh, cat, delim=("-" * 20)) if nstopped == 0: if cat.sync(): report("! %s (%d)" % (path, nhybridized)) else: warning( n_("@info", "%(num)d message in '%(file)s' cannot be " "cleanly hybridized.", "%(num)d messages in '%(file)s' cannot be " "cleanly hybridized.", num=nstopped, file=path)) nhybridized = 0 return nhybridized
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 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 apply_ediff(op): # Read the ediff PO. dummy_stream_path = "<stdin>" 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))