Exemple #1
0
def initialize_confs(conf):
    """Process configuration file"""
    config_file = os.path.expanduser(conf)
    # Allow inline comment with #
    confs_i = configparser.ConfigParser(inline_comment_prefixes=("#"))
    confs_i.read_string(config_template)
    confs_f = configparser.ConfigParser(inline_comment_prefixes=("#"))
    if os.path.exists(config_file):
        confs_f.read(config_file)
        if ("version" in confs_f["config"].keys() and
                confs_f["config"]["version"] == confs_i["config"]["version"]):
            confs = confs_f
        else:
            error_exit('''\
Error in {0}: version mismatch
        the current version:  {1}
        the required version: {2}

Rename {0} to {0}.bkup and make the new {0} by
editing the template obtained by "imediff -t"'''.format(
                conf, confs_f["config"]["version"],
                confs_i["config"]["version"]))
    else:
        confs = confs_i
    return confs
Exemple #2
0
    def __init__(
        self,
        a=[],
        b=[],
        linerule=2,
    ):
        """
        Construct a _LineMatcher

        """

        # initialize
        self.a = a
        self.b = b
        if not (linerule >= 0 and linerule < 20):
            error_exit("E: linerule should be between 0 and 19 but {}".format(
                linerule))
        # linerule:
        # 0      r""        -- drop none between text, but strip
        # 1      r"\s+"     -- drop all whitespaces
        # 2      r"[\s\"']" -- drop all whitespaces and quotes (default)
        # 3      r"\W+"     -- drop all non-alphanumerics
        # 10     r""        -- drop none between text, but strip and lowercase
        # 11     r"\s+"     -- drop all whitespaces and lowercase
        # 12     r"[\s\"']" -- drop all whitespaces and quotes and lowercase
        # 13     r"\W+"     -- drop all non-alphanumerics and lowercase
        if (linerule % 10) == 0:
            re_preform = re.compile(r"")
        elif (linerule % 10) == 1:
            re_preform = re.compile(r"\s+")
        elif (linerule % 10) == 2:
            re_preform = re.compile(r"[\s\"']+")
        elif (linerule % 10) == 3:
            re_preform = re.compile(r"\W+")
        else:
            re_preform = re.compile(r"")
        self.a_int = []
        for ax in a:
            if linerule < 10:
                filtered_ax = re_preform.sub("", ax).strip()
            else:
                filtered_ax = re_preform.sub("", ax).strip().lower()
            self.a_int.append(filtered_ax)
        self.b_int = []
        for bx in b:
            if linerule < 10:
                filtered_bx = re_preform.sub("", bx).strip()
            else:
                filtered_bx = re_preform.sub("", bx).strip().lower()
            self.b_int.append(filtered_bx)
        self.int = _LineMatcher(self.a_int, self.b_int, 0, len(self.a_int), 0,
                                len(self.b_int))
Exemple #3
0
def create_template(conf):
    config_file = os.path.expanduser(conf)
    if not os.path.exists(config_file):
        logger.debug("create configuration file: {}".format(conf))
        try:
            with open(config_file, mode="w",
                      buffering=io.DEFAULT_BUFFER_SIZE) as ofp:
                ofp.write(config_template)
        except IOError:
            error_exit("Error in creating configuration file: {}".format(conf))
    else:
        error_exit("Erase {} before 'imediff -t'".format(conf))
    return
Exemple #4
0
 def get_content(self, i):
     """Return content based on mode"""
     (tag, i1, i2, j1, j2, k1, k2, mode, row, bf) = self.opcodes[i]
     if tag == "E" or tag == "e":
         content = self.list_a[i1:i2]
     elif mode == "a":
         content = self.list_a[i1:i2]
     elif mode == "b":
         content = self.list_b[j1:j2]
     elif mode == "c":
         content = self.list_c[k1:k2]
     elif mode == "d":
         content = self.merge_diff(i)
     elif mode == "e":
         if bf is not None:
             content = bf
         else:
             error_exit("Bad mode='e' with missing edited buffer text\n")
     elif mode == "f":
         if self.diff_mode == 2:
             content = self.merge_wdiff2(i)
         else:  # self.diff_mode == 3
             (clean_merge, content) = self.merge_wdiff3(i)
     elif mode == "g":
         if self.diff_mode == 2:
             content = self.merge_diff(i)
         else:  # self.diff_mode == 3
             (clean_merge, content) = self.merge_wdiff3(i)
     else:
         error_exit("Bad mode='{}'\n".format(mode))
     # content is at least [] (at least empty list)
     if content is None:
         error_exit("content can't be None")
     return content
Exemple #5
0
 def get_opcodes(self):
     if self.depth == 0:  # depth = 0
         side = 0
         logger.debug(
             "===  a[{}:{}]/b[{}:{}]  ===  d={:02d}  ===  line[:]  ===".
             format(self.is1, self.is2, self.js1, self.js2, self.depth))
     elif self.depth > 0:  # depth > 0
         if self.depth % 2 == 1:  # depth = 1, 3, 5, ...
             side = +1
             logger.debug(
                 "===  a[{}:{}]/b[{}:{}]  ===  d={:02d}  ===  line[:{:02d}] ==="
                 .format(self.is1, self.is2, self.js1, self.js2, self.depth,
                         self.length))
         else:  # self.depth % 2 == 0:  # depth = 2, 4, 6, ...
             side = -1
             logger.debug(
                 "===  a[{}:{}]/b[{}:{}]  ===  d={:02d}  ===  line[-{:02d}:] ==="
                 .format(self.is1, self.is2, self.js1, self.js2, self.depth,
                         self.length))
     else:
         error_exit(
             "===  a[{}:{}]/b[{}:{}]  ===  d={:02d} should be non-negative".
             format(self.is1, self.is2, self.js1, self.js2, self.depth))
     if side == 0:
         # self.is1, self.is2, self.js1, self.js2 are known to cover all
         am = self.a
         bm = self.b
     elif side == 1:  # left side match
         am = []
         bm = []
         for i in range(self.is1, self.is2):
             am.append(self.a[i][:self.length])
         for j in range(self.js1, self.js2):
             bm.append(self.b[j][:self.length])
     else:  # side == -1, right side match
         am = []
         bm = []
         for i in range(self.is1, self.is2):
             am.append(self.a[i][-self.length:])
         for j in range(self.js1, self.js2):
             bm.append(self.b[j][-self.length:])
     for i in range(self.is1, self.is2):
         logger.debug("filter a[{}] -> am[{}]='{}'".format(
             i, i - self.is1, am[i - self.is1]))
     for j in range(self.js1, self.js2):
         logger.debug("filter b[{}] -> bm[{}]='{}'".format(
             j, j - self.js1, bm[j - self.js1]))
     seq = SequenceMatcher(None, am, bm)
     match = []
     for tag, i1, i2, j1, j2 in seq.get_opcodes():
         logger.debug(">>> tag={}  ===  a[{}:{}]/b[{}:{}]".format(
             tag, i1 + self.is1, i2 + self.is1, j1 + self.js1,
             j2 + self.js1))
         if tag == "equal":
             for i in range(i1, i2):
                 ip = self.is1 + i
                 jp = self.js1 + j1 + i - i1
                 if side == 0:
                     # full match on filtered line
                     match.append(("E", ip, ip + 1, jp, jp + 1))
                 else:
                     # partial match only (F for fuzzy)
                     match.append(("F", ip, ip + 1, jp, jp + 1))
         elif i1 == i2 or j1 == j2:
             # clean insert/delete
             match.append(("N", i1 + self.is1, i2 + self.is1, j1 + self.js1,
                           j2 + self.js1))
         elif (i1 + 1) == i2 and (j1 + 1) == j2:
             # single line change -> assume fuzzy match without checking
             match.append(("F", i1 + self.is1, i2 + self.is1, j1 + self.js1,
                           j2 + self.js1))
         else:
             # dig deeper for multi-line changes
             if side == 0:
                 # full -> left side
                 match.extend(
                     _LineMatcher(
                         a=self.a,
                         b=self.b,
                         is1=i1 + self.is1,
                         is2=i2 + self.is1,
                         js1=j1 + self.js1,
                         js2=j2 + self.js1,
                         length=self.length,
                         depth=self.depth + 1,
                     ).get_opcodes())
             elif side == +1:
                 # left side -> right side
                 match.extend(
                     _LineMatcher(
                         a=self.a,
                         b=self.b,
                         is1=i1 + self.is1,
                         is2=i2 + self.is1,
                         js1=j1 + self.js1,
                         js2=j2 + self.js1,
                         length=self.length,
                         depth=self.depth + 1,
                     ).get_opcodes())
             elif self.length > self.lenmin:  # side == -1
                 # right side -> left side (shorter)
                 match.extend(
                     _LineMatcher(
                         a=self.a,
                         b=self.b,
                         is1=i1 + self.is1,
                         is2=i2 + self.is1,
                         js1=j1 + self.js1,
                         js2=j2 + self.js1,
                         length=self.length * self.factor // 10,
                         depth=self.depth + 1,
                     ).get_opcodes())
             else:
                 # no more shorter, give up as multi-line block change
                 match.append((
                     "N",
                     i1 + self.is1,
                     i2 + self.is1,
                     j1 + self.js1,
                     j2 + self.js1,
                 ))
         logger.debug("<<< tag={}  ===  a[{}:{}]/b[{}:{}]".format(
             tag, i1 + self.is1, i2 + self.is1, j1 + self.js1,
             j2 + self.js1))
     return match
Exemple #6
0
def main():
    """
    Entry point for imediff command

    Exit value
        0       program exits normally after saving data
        1       program quits without saving
        2       program terminates after an internal error
    """

    # preparation and arguments
    locale.setlocale(locale.LC_ALL, "")
    args = initialize_args()
    if args.template:
        create_template(args.conf)
        sys.exit(0)

    # logging
    logger.setLevel(logging.DEBUG)
    if args.debug:
        # create file handler which logs even debug messages
        fh = logging.FileHandler("imediff.log")
    else:
        # create file handler which doesn't log
        fh = logging.NullHandler()
    fh.setLevel(logging.DEBUG)
    # create console handler with a higher log level
    ch = logging.StreamHandler()
    ch.setLevel(logging.ERROR)
    # create formatter and add it to the handlers
    formatter = logging.Formatter(
        "%(asctime)s - %(name)s - %(levelname)s - %(message)s")
    fh.setFormatter(formatter)
    ch.setFormatter(formatter)
    # add the handlers to the logger
    logger.addHandler(fh)
    logger.addHandler(ch)

    # configuration
    confs = initialize_confs(args.conf)
    editor = "editor"
    if "EDITOR" in os.environ:
        editor = os.environ["EDITOR"]
    if "editor" in confs["config"].keys():
        editor = confs["config"]["editor"]
    args.edit_cmd = shutil.which(editor)
    if args.edit_cmd is None:
        args.edit_cmd = "/usr/bin/editor"  # safe fall back
    logger.debug("external editor {} found as {}".format(
        editor, args.edit_cmd))

    # normalize and process non-standard situation
    if args.version:
        print(_version)
        sys.exit(0)
    if args.diff_mode == 0 and args.non_interactive:
        tutorial = True
    if args.diff_mode == 0:
        args.diff_mode = 3  # Fake input
        list_a = (_opening +
                  '\n    Type "q" to quit this tutorial.').splitlines(
                      keepends=True)
        list_b = [""]
        list_c = list_a
        confs["config"]["confirm_quit"] = "False"
        confs["config"]["confirm_exit"] = "False"
        tutorial = True
    elif args.diff_mode == 2:
        # diff2
        list_a = read_lines(args.file_a)
        list_b = read_lines(args.file_b)
        list_c = None
        tutorial = False
    elif args.diff_mode == 3:
        list_a = read_lines(args.file_a)
        list_b = read_lines(args.file_b)
        list_c = read_lines(args.file_c)
        tutorial = False
    else:
        error_exit("imediff normally takes 2 or 3 files")

    # call main routine
    if not args.non_interactive:
        display_instance = TextPad(list_a, list_b, list_c, args, confs)
        display_instance.command_loop(tutorial=tutorial)
        del display_instance
    elif tutorial:
        print(_opening)
    else:
        text_instance = TextData(list_a, list_b, list_c, args, confs)
        text_instance.command_loop()
        del text_instance
    sys.exit(0)
Exemple #7
0
 def gui_loop(self, stdscr):  # for curses TUI (core)
     # initialize
     self.stdscr = stdscr
     color = self.color  # shorthand
     self.winh, self.winw = self.stdscr.getmaxyx()  # window size
     curses.start_color()
     self.stdscr.clear()
     self.stdscr.refresh()
     # set color pair_number as (pair_number, fg, bg)
     curses.init_pair(1, cc[color["color_a"]], cc["BLACK"])
     curses.init_pair(2, cc[color["color_b"]], cc["BLACK"])
     curses.init_pair(3, cc[color["color_c"]], cc["BLACK"])
     curses.init_pair(4, cc[color["color_d"]], cc["BLACK"])
     curses.init_pair(5, cc[color["color_e"]], cc["BLACK"])
     curses.init_pair(6, cc[color["color_f"]], cc["BLACK"])
     #
     # +6: active cc
     self.active_color = 6
     curses.init_pair(7, cc["WHITE"], cc[color["color_a"]])
     curses.init_pair(8, cc["WHITE"], cc[color["color_b"]])
     curses.init_pair(9, cc["WHITE"], cc[color["color_c"]])
     curses.init_pair(10, cc["WHITE"], cc[color["color_d"]])
     curses.init_pair(11, cc["WHITE"], cc[color["color_e"]])
     curses.init_pair(12, cc["WHITE"], cc[color["color_f"]])
     #
     # +12: deleted cc
     self.deleted_color = 12
     curses.init_pair(13, cc[color["color_a"]], cc["WHITE"])
     curses.init_pair(14, cc[color["color_b"]], cc["WHITE"])
     curses.init_pair(15, cc[color["color_c"]], cc["WHITE"])
     curses.init_pair(16, cc[color["color_d"]], cc["WHITE"])
     curses.init_pair(17, cc[color["color_e"]], cc["WHITE"])
     curses.init_pair(18, cc[color["color_f"]], cc["WHITE"])
     #
     # +6+12
     curses.init_pair(19, cc["BLACK"], cc[color["color_a"]])
     curses.init_pair(20, cc["BLACK"], cc[color["color_b"]])
     curses.init_pair(21, cc["BLACK"], cc[color["color_c"]])
     curses.init_pair(22, cc["BLACK"], cc[color["color_d"]])
     curses.init_pair(23, cc["BLACK"], cc[color["color_e"]])
     curses.init_pair(24, cc["BLACK"], cc[color["color_f"]])
     #
     if curses.has_colors() == False:
         self.mono = True
     if self.mono:
         self.mode = True
         self.color_a = "WHITE"
         self.color_b = "WHITE"
         self.color_c = "WHITE"
         self.color_d = "WHITE"
         self.color_e = "WHITE"
         self.color_f = "WHITE"
     else:
         if self.diff_mode == 2:
             self.color_a = color["color_a"]
             self.color_b = color["color_b"]
             # self.color_c = color['color_c'] # never used
             self.color_d = color["color_d"]
             self.color_e = color["color_e"]
             self.color_f = color["color_f"]
         else:
             self.color_a = color["color_a"]
             self.color_b = color["color_b"]
             self.color_c = color["color_c"]
             self.color_d = color["color_d"]
             self.color_e = color["color_e"]
             self.color_f = color["color_f"]
     # display parameters
     self.col = 0  # the column coordinate of textpad (left most=0)
     self.row = 0  # the row coordinate of textpad    (top most=0)
     self.update_textpad = True  # update textpad content
     while True:
         if self.active is not None:
             logger.debug(
                 "command loop: active = {} active_index = {} row = {} col = {}"
                 .format(self.active, self.actives[self.active], self.row,
                         self.col))
         else:
             logger.debug(
                 "command loop: active = ***None*** row = {} col = {}".
                 format(self.row, self.col))
         curses.curs_set(0)
         if self.update_textpad:
             self.new_textpad()
         # clear to remove garbage outside of textpad
         self.winh, self.winw = self.stdscr.getmaxyx()
         self.adjust_window()
         for icol in range(self.contw - self.col, self.winw):
             self.stdscr.vline(0, icol, " ", self.winh)
         ##self.stdscr.vline(0, self.contw - self.col, '@', self.winh)
         ##self.stdscr.vline(0, self.winw-1, '*', self.winh)
         # clear rows downward to remove garbage characters
         for irow in range(self.conth - self.row, self.winh):
             self.stdscr.hline(irow, 0, " ", self.winw)
         ##if (self.conth - self.row) <= self.winh -1 and (self.conth - self.row) >= 0:
         ##    self.stdscr.hline(self.conth - self.row , 0, '@', self.winw)
         if self.update_textpad or self.update_active:
             self.highlight()
         self.textpad.refresh(self.row, self.col, 0, 0, self.winh - 1,
                              self.winw - 1)
         if self.active is not None:
             row = self.get_row(self.actives[self.active]) - self.row
             if row >= 0 and row < self.winh:
                 self.stdscr.move(row, 0)
                 curses.curs_set(1)
             else:
                 curses.curs_set(0)
         self.stdscr.refresh()
         # reset flags
         self.update_textpad = False
         self.update_active = False
         if self.tutorial:
             c = ord("H")
             self.tutorial = False
         else:
             c = self.getch_translated()
         ch = chr(c)
         if ch == "w" or ch == "x" or c == curses.KEY_EXIT or c == curses.KEY_SAVE:
             if self.sloppy or (not self.sloppy
                                and self.get_unresolved_count() == 0):
                 if not self.confirm_exit or self.popup(
                         _("Do you 'save and exit'? (Press '{y:c}' to exit)"
                           ).format(y=self.rkc["y"])):
                     output = self.get_output()
                     write_file(self.file_o, output)
                     break
             else:
                 self.popup(
                     _("Can't 'save and exit' due to the non-clean merge. (Press '{y:c}' to continue)"
                       + "\n\n" + _nonclean).format(y=self.rkc["y"]))
         elif ch == "q":
             if not self.confirm_exit or self.popup(
                     _("Do you 'quit without saving'? (Press '{y:c}' to quit)"
                       ).format(y=self.rkc["y"])):
                 self.opcodes = []
                 error_exit("Quit without saving by the user request\n")
         elif ch == "h" or c == curses.KEY_HELP:
             # Show help screen
             self.popup(self.helptext())
         elif ch == "H":
             # Show tutorial screen
             self.popup(_tutorial)
         elif ch == "s" or ch == "?":
             # Show location
             if len(self.actives) == 0:
                 self.popup(
                     _stattext0.format(row=self.row,
                                       conth=self.conth,
                                       col=self.col))
             else:
                 self.popup(
                     _stattext1.format(
                         active=self.active,
                         total=len(self.actives),
                         unresolved=self.get_unresolved_count(),
                         row=self.row,
                         conth=self.conth,
                         col=self.col,
                     ))
         # Moves in document
         elif c == curses.KEY_SR or c == curses.KEY_UP or ch == "k":
             self.row -= 1
         elif c == curses.KEY_SF or c == curses.KEY_DOWN or ch == "j":
             self.row += 1
         elif c == curses.KEY_LEFT:
             self.col -= 8
         elif c == curses.KEY_RIGHT:
             self.col += 8
         elif c == curses.KEY_PPAGE:
             self.row -= self.winh
         elif c == curses.KEY_NPAGE:
             self.row += self.winh
         # Terminal resize signal
         elif c == curses.KEY_RESIZE:
             self.winh, self.winw = self.stdscr.getmaxyx()
         else:
             pass
         # Following key-command updates TextPad
         if self.active is not None:
             # get active chunk
             # Explicitly select chunk mode
             if ch in "abdef":
                 self.set_mode(self.actives[self.active], ch)
             elif ch in "12456":
                 self.set_mode(self.actives[self.active],
                               chr(ord(ch) - ord("1") + ord("a")))
             elif ch in "ABDEF":
                 self.set_all_mode(ch.lower())
             elif ch in "cg" and self.diff_mode == 3:
                 self.set_mode(self.actives[self.active], ch)
             elif ch in "37" and self.diff_mode == 3:
                 self.set_mode(self.actives[self.active],
                               chr(ord(ch) - ord("1") + ord("a")))
             elif ch in "CG" and self.diff_mode == 3:
                 self.set_all_mode(ch.lower())
             elif c == 10 or c == curses.KEY_COMMAND:
                 mode = self.get_mode(self.actives[self.active])
                 if mode == "a":
                     self.set_mode(self.actives[self.active], "b")
                 elif mode == "b" and self.diff_mode == 2:
                     self.set_mode(self.actives[self.active], "d")
                 elif mode == "b" and self.diff_mode == 3:
                     self.set_mode(self.actives[self.active], "c")
                 elif mode == "c":
                     self.set_mode(self.actives[self.active], "d")
                 elif (mode == "d" and self.get_bf(
                         self.actives[self.active]) is not None):
                     self.set_mode(self.actives[self.active], "e")
                 elif mode == "d":
                     self.set_mode(self.actives[self.active], "f")
                 elif mode == "e":
                     self.set_mode(self.actives[self.active], "f")
                 else:  # f
                     self.set_mode(self.actives[self.active], "a")
             elif ch == "m":
                 self.editor(self.actives[self.active])
             elif ch == "M" and mode == "e":
                 self.del_editor(self.actives[self.active])
             elif ch == "n" or c == curses.KEY_NEXT or ch == " ":
                 self.active_next()
             elif ch == "p" or c == curses.KEY_PREVIOUS or c == curses.KEY_BACKSPACE:
                 self.active_prev()
             elif ch == "t" or c == curses.KEY_HOME:
                 self.active_home()
             elif ch == "z" or c == curses.KEY_END:
                 self.active_end()
             elif ch == "N" or ch == "\t":
                 self.diff_next()
             elif ch == "P" or c == curses.KEY_BTAB:
                 self.diff_prev()
             elif ch == "T":
                 self.diff_home()
             elif ch == "Z":
                 self.diff_end()
             else:
                 pass
         logger.debug("command-loop")
     return