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))
def _norm_ui_cat (cat, xmlescape): norm_cat = Catalog("", create=True, monitored=False) norm_cat.filename = cat.filename + "~norm" # Normalize messages and collect them by normalized keys. msgs_by_normkey = {} for msg in cat: if msg.obsolete: continue orig_msgkey = (msg.msgctxt, msg.msgid) remove_markup_msg(msg, cat) # before accelerator removal remove_accel_msg(msg, cat) # after markup removal normkey = (msg.msgctxt, msg.msgid) if normkey not in msgs_by_normkey: msgs_by_normkey[normkey] = [] msgs_by_normkey[normkey].append((msg, orig_msgkey)) for msgs in msgs_by_normkey.values(): # If there are several messages with same normalized key and # different translations, add extra disambiguations to context. # These disambiguations must not depend on message ordering. if len(msgs) > 1: # Check equality of translations. msgstr0 = u"" for msg, d1 in msgs: if msg.translated: if not msgstr0: msgstr0 = msg.msgstr[0] elif msgstr0 != msg.msgstr[0]: msgstr0 = None break if msgstr0 is None: # disambiguation necessary tails = set() for msg, (octxt, omsgid) in msgs: if msg.msgctxt is None: msg.msgctxt = u"" tail = hashlib.md5(omsgid).hexdigest() n = 4 # minimum size of the disambiguation tail while tail[:n] in tails: n += 1 if n > len(tail): raise PologyError( _("@info", "Hash function has returned same result " "for two different strings.")) tails.add(tail[:n]) msg.msgctxt += "~" + tail[:n] else: # all messages have same translation, use first msgs = msgs[:1] # Escape text fields. if xmlescape: for msg, d1 in msgs: if msg.msgctxt: msg.msgctxt = _escape_to_xml(msg.msgctxt) msg.msgid = _escape_to_xml(msg.msgid) if msg.msgid_plural: msg.msgid_plural = _escape_to_xml(msg.msgid_plural) for i in range(len(msg.msgstr)): msg.msgstr[i] = _escape_to_xml(msg.msgstr[i]) # Add normalized messages to normalized catalog. for msg, d1 in msgs: if msg.msgctxt or msg.msgid: norm_cat.add_last(msg) return norm_cat