import argparse parser = argparse.ArgumentParser(description="Prints to stdout the patched document") parser.add_argument('original', help='original document') parser.add_argument('patch', help='patch file') args = parser.parse_args() fa = open(args.original,'r') la = fa.read().split('\n') # a workspace for us to edit, each element being a list that 1) originally contains a corresponding line # in a, 2) may be emptied if that line is removed, 3) may be where we insert new content ws = [[l] for l in la] fp = open(args.patch,'r') lp = fp.read().split('\n') def str2intv(s): if "," in s: x, y = s.split(',') else: x, y = s, s return int(x)-1, int(y) def ws_rm(desc, lp, nb): l,r = str2intv(desc) for p in range(l,r): nb += 1 assert lp[nb][:2] == "< " assert ws[p][0] == lp[nb][2:],\ f"Patch file wants to delete '{lp[nb][2:]}', but originally line {p} is '{ws[p][0]}'" ws[p] = list() return nb + 1, l def ws_insert(p, desc, lp, nb): l,r = str2intv(desc) for _ in range(l,r): nb += 1 assert lp[nb][:2] == "> " ws[p].append(lp[nb][2:]) return nb + 1 nb = 0 while nb < len(lp): line = lp[nb] if line: assert line[0] != '<' and line[0] != '>', f"Expected description at line {nb}, but got {line}" else: nb += 1 continue idx_d = line.find('d') if idx_d != -1: nb, _ = ws_rm(line[:idx_d], lp, nb) continue idx_a = line.find('a') if idx_a != -1: p = int(line[:idx_a]) - 1 nb = ws_insert(p, line[idx_a+1:], lp, nb) continue idx_c = line.find('c') if idx_c != -1: nb, p = ws_rm(line[:idx_c], lp, nb) nb = ws_insert(p, line[idx_c+1:], lp, nb) continue assert False, f"Unreachable. Cannot parse line {nb}: {line}" flatws = [line for _ in ws for line in _] print('\n'.join(flatws), end="") # Don't want to print additional new line