[meld] Create new matchers package for diff-related code



commit 9ea7d2707eae443fca198119165d6d1e4fa93c0a
Author: Kai Willadsen <kai willadsen gmail com>
Date:   Sun Oct 9 07:41:41 2016 +1000

    Create new matchers package for diff-related code
    
    This commit also split the threading-related code for handling
    asynchronous diffs into a new meld.matchers.helpers module.

 meld/filediff.py                        |   97 +------------------------------
 meld/filemerge.py                       |    2 +-
 meld/{ => matchers}/diffutil.py         |    4 +-
 meld/matchers/helpers.py                |   96 ++++++++++++++++++++++++++++++
 meld/{ => matchers}/merge.py            |    6 +-
 meld/{matchers.py => matchers/myers.py} |    0
 setup.py                                |    1 +
 setup_win32.py                          |    1 +
 test/test_gutterrendererchunk.py        |    2 +-
 test/test_matchers.py                   |   14 ++--
 10 files changed, 115 insertions(+), 108 deletions(-)
---
diff --git a/meld/filediff.py b/meld/filediff.py
index 91c7b30..16d0815 100644
--- a/meld/filediff.py
+++ b/meld/filediff.py
@@ -16,12 +16,8 @@
 
 import copy
 import functools
-import logging
 import math
 import os
-import queue
-import threading
-import time
 
 from gi.repository import GLib
 from gi.repository import GObject
@@ -31,11 +27,10 @@ from gi.repository import Gtk
 from gi.repository import GtkSource
 
 from meld.conf import _
-from . import diffutil
-from . import matchers
+from meld.matchers import diffutil
 from . import meldbuffer
 from . import melddoc
-from . import merge
+from meld.matchers import merge
 from . import misc
 from . import patchdialog
 from . import recent
@@ -44,36 +39,11 @@ from .ui import findbar
 from .ui import gnomeglade
 
 from meld.const import MODE_REPLACE, MODE_DELETE, MODE_INSERT, NEWLINES
+from meld.matchers.helpers import CachedSequenceMatcher
 from meld.settings import bind_settings, meldsettings
 from meld.sourceview import LanguageManager, get_custom_encoding_candidates
 
 
-log = logging.getLogger(__name__)
-
-
-class MatcherWorker(threading.Thread):
-
-    matcher_class = matchers.InlineMyersSequenceMatcher
-
-    def __init__(self, tasks, results):
-        super(MatcherWorker, self).__init__()
-        self.tasks = tasks
-        self.results = results
-        self.daemon = True
-
-    def run(self):
-        while True:
-            task_id, (text1, textn) = self.tasks.get()
-            try:
-                matcher = self.matcher_class(None, text1, textn)
-                self.results.put((task_id, matcher.get_opcodes()))
-            except Exception as e:
-                log.error("Exception while running diff: %s", e)
-            finally:
-                self.tasks.task_done()
-            time.sleep(0)
-
-
 def with_focused_pane(function):
     @functools.wraps(function)
     def wrap_function(*args, **kwargs):
@@ -84,67 +54,6 @@ def with_focused_pane(function):
     return wrap_function
 
 
-class CachedSequenceMatcher(object):
-    """Simple class for caching diff results, with LRU-based eviction
-
-    Results from the SequenceMatcher are cached and timestamped, and
-    subsequently evicted based on least-recent generation/usage. The LRU-based
-    eviction is overly simplistic, but is okay for our usage pattern.
-    """
-
-    def __init__(self):
-        self.cache = {}
-        self.tasks = queue.Queue()
-        # Limiting the result queue here has the effect of giving us
-        # much better interactivity. Without this limit, the
-        # result-checker tends to get starved and all highlights get
-        # delayed until we're almost completely finished.
-        self.results = queue.Queue(5)
-        self.thread = MatcherWorker(self.tasks, self.results)
-        self.task_id = 1
-        self.queued_matches = {}
-        GLib.idle_add(self.thread.start)
-
-    def match(self, text1, textn, cb):
-        texts = (text1, textn)
-        try:
-            self.cache[texts][1] = time.time()
-            opcodes = self.cache[texts][0]
-            GLib.idle_add(lambda: cb(opcodes))
-        except KeyError:
-            GLib.idle_add(lambda: self.enqueue_task(texts, cb))
-
-    def enqueue_task(self, texts, cb):
-        if not bool(self.queued_matches):
-            GLib.idle_add(self.check_results)
-        self.queued_matches[self.task_id] = (texts, cb)
-        self.tasks.put((self.task_id, texts))
-        self.task_id += 1
-
-    def check_results(self):
-        try:
-            task_id, opcodes = self.results.get_nowait()
-            texts, cb = self.queued_matches.pop(task_id)
-            self.cache[texts] = [opcodes, time.time()]
-            GLib.idle_add(lambda: cb(opcodes))
-        except queue.Empty:
-            pass
-
-        return bool(self.queued_matches)
-
-    def clean(self, size_hint):
-        """Clean the cache if necessary
-
-        @param size_hint: the recommended minimum number of cache entries
-        """
-        if len(self.cache) < size_hint * 3:
-            return
-        items = list(self.cache.items())
-        items.sort(key=lambda it: it[1][1])
-        for item in items[:-size_hint * 2]:
-            del self.cache[item[0]]
-
-
 MASK_SHIFT, MASK_CTRL = 1, 2
 PANE_LEFT, PANE_RIGHT = -1, +1
 
diff --git a/meld/filemerge.py b/meld/filemerge.py
index 6ef35c1..6777ba4 100644
--- a/meld/filemerge.py
+++ b/meld/filemerge.py
@@ -16,7 +16,7 @@
 
 from meld.conf import _
 from . import filediff
-from . import merge
+from meld.matchers import merge
 from . import recent
 
 
diff --git a/meld/matchers/__init__.py b/meld/matchers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/meld/diffutil.py b/meld/matchers/diffutil.py
similarity index 99%
rename from meld/diffutil.py
rename to meld/matchers/diffutil.py
index 2d7de02..2b20fa8 100644
--- a/meld/diffutil.py
+++ b/meld/matchers/diffutil.py
@@ -16,8 +16,8 @@
 
 from gi.repository import GObject
 
-from .matchers import DiffChunk, MyersSequenceMatcher, \
-    SyncPointMyersSequenceMatcher
+from meld.matchers.myers import (
+    DiffChunk, MyersSequenceMatcher, SyncPointMyersSequenceMatcher)
 
 
 opcode_reverse = {
diff --git a/meld/matchers/helpers.py b/meld/matchers/helpers.py
new file mode 100644
index 0000000..86c23ac
--- /dev/null
+++ b/meld/matchers/helpers.py
@@ -0,0 +1,96 @@
+
+import logging
+import queue
+import threading
+import time
+
+from gi.repository import GLib
+
+from meld.matchers import myers
+
+
+log = logging.getLogger(__name__)
+
+
+class MatcherWorker(threading.Thread):
+
+    matcher_class = myers.InlineMyersSequenceMatcher
+
+    def __init__(self, tasks, results):
+        super(MatcherWorker, self).__init__()
+        self.tasks = tasks
+        self.results = results
+        self.daemon = True
+
+    def run(self):
+        while True:
+            task_id, (text1, textn) = self.tasks.get()
+            try:
+                matcher = self.matcher_class(None, text1, textn)
+                self.results.put((task_id, matcher.get_opcodes()))
+            except Exception as e:
+                log.error("Exception while running diff: %s", e)
+            finally:
+                self.tasks.task_done()
+            time.sleep(0)
+
+
+class CachedSequenceMatcher(object):
+    """Simple class for caching diff results, with LRU-based eviction
+
+    Results from the SequenceMatcher are cached and timestamped, and
+    subsequently evicted based on least-recent generation/usage. The LRU-based
+    eviction is overly simplistic, but is okay for our usage pattern.
+    """
+
+    def __init__(self):
+        self.cache = {}
+        self.tasks = queue.Queue()
+        # Limiting the result queue here has the effect of giving us
+        # much better interactivity. Without this limit, the
+        # result-checker tends to get starved and all highlights get
+        # delayed until we're almost completely finished.
+        self.results = queue.Queue(5)
+        self.thread = MatcherWorker(self.tasks, self.results)
+        self.task_id = 1
+        self.queued_matches = {}
+        GLib.idle_add(self.thread.start)
+
+    def match(self, text1, textn, cb):
+        texts = (text1, textn)
+        try:
+            self.cache[texts][1] = time.time()
+            opcodes = self.cache[texts][0]
+            GLib.idle_add(lambda: cb(opcodes))
+        except KeyError:
+            GLib.idle_add(lambda: self.enqueue_task(texts, cb))
+
+    def enqueue_task(self, texts, cb):
+        if not bool(self.queued_matches):
+            GLib.idle_add(self.check_results)
+        self.queued_matches[self.task_id] = (texts, cb)
+        self.tasks.put((self.task_id, texts))
+        self.task_id += 1
+
+    def check_results(self):
+        try:
+            task_id, opcodes = self.results.get_nowait()
+            texts, cb = self.queued_matches.pop(task_id)
+            self.cache[texts] = [opcodes, time.time()]
+            GLib.idle_add(lambda: cb(opcodes))
+        except queue.Empty:
+            pass
+
+        return bool(self.queued_matches)
+
+    def clean(self, size_hint):
+        """Clean the cache if necessary
+
+        @param size_hint: the recommended minimum number of cache entries
+        """
+        if len(self.cache) < size_hint * 3:
+            return
+        items = list(self.cache.items())
+        items.sort(key=lambda it: it[1][1])
+        for item in items[:-size_hint * 2]:
+            del self.cache[item[0]]
diff --git a/meld/merge.py b/meld/matchers/merge.py
similarity index 98%
rename from meld/merge.py
rename to meld/matchers/merge.py
index 9c454ed..357576d 100644
--- a/meld/merge.py
+++ b/meld/matchers/merge.py
@@ -13,13 +13,13 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-from . import diffutil
-from . import matchers
+from meld.matchers import diffutil
+from meld.matchers.myers import MyersSequenceMatcher
 
 
 class AutoMergeDiffer(diffutil.Differ):
 
-    _matcher = matchers.MyersSequenceMatcher
+    _matcher = MyersSequenceMatcher
     # _matcher = PatienceSequenceMatcher
 
     def __init__(self):
diff --git a/meld/matchers.py b/meld/matchers/myers.py
similarity index 100%
rename from meld/matchers.py
rename to meld/matchers/myers.py
diff --git a/setup.py b/setup.py
index 588978b..05f0af6 100755
--- a/setup.py
+++ b/setup.py
@@ -30,6 +30,7 @@ setup(
     keywords=['diff', 'merge'],
     packages=[
         'meld',
+        'meld.matchers',
         'meld.ui',
         'meld.vc',
     ],
diff --git a/setup_win32.py b/setup_win32.py
index afc0761..c3dc9a5 100644
--- a/setup_win32.py
+++ b/setup_win32.py
@@ -130,6 +130,7 @@ setup(
     ],
     packages=[
         'meld',
+        'meld.matchers',
         'meld.ui',
         'meld.vc',
     ],
diff --git a/test/test_gutterrendererchunk.py b/test/test_gutterrendererchunk.py
index 02d8ace..e101ee4 100644
--- a/test/test_gutterrendererchunk.py
+++ b/test/test_gutterrendererchunk.py
@@ -5,7 +5,7 @@ import pytest
 import meld.gutterrendererchunk
 from meld.gutterrendererchunk import GutterRendererChunkAction
 from meld.const import MODE_REPLACE, MODE_DELETE, MODE_INSERT
-from meld.matchers import DiffChunk
+from meld.matchers.myers import DiffChunk
 
 
 def make_chunk(chunk_type):
diff --git a/test/test_matchers.py b/test/test_matchers.py
index 955d420..91c36c0 100644
--- a/test/test_matchers.py
+++ b/test/test_matchers.py
@@ -1,6 +1,6 @@
 
 import unittest
-from meld import matchers
+from meld.matchers import myers
 
 
 class MatchersTests(unittest.TestCase):
@@ -9,7 +9,7 @@ class MatchersTests(unittest.TestCase):
         a = list('abcbdefgabcdefg')
         b = list('gfabcdefcd')
         r = [(0, 2, 3), (4, 5, 3), (10, 8, 2), (15, 10, 0)]
-        matcher = matchers.MyersSequenceMatcher(None, a, b)
+        matcher = myers.MyersSequenceMatcher(None, a, b)
         blocks = matcher.get_matching_blocks()
         self.assertEqual(blocks, r)
 
@@ -17,7 +17,7 @@ class MatchersTests(unittest.TestCase):
         a = list('abcfabgcd')
         b = list('afabcgabgcabcd')
         r = [(0, 2, 3), (4, 6, 3), (7, 12, 2), (9, 14, 0)]
-        matcher = matchers.MyersSequenceMatcher(None, a, b)
+        matcher = myers.MyersSequenceMatcher(None, a, b)
         blocks = matcher.get_matching_blocks()
         self.assertEqual(blocks, r)
 
@@ -25,7 +25,7 @@ class MatchersTests(unittest.TestCase):
         a = 'red, blue, yellow, white'
         b = 'black green, hue, white'
         r = [(17, 16, 7), (24, 23, 0)]
-        matcher = matchers.InlineMyersSequenceMatcher(None, a, b)
+        matcher = myers.InlineMyersSequenceMatcher(None, a, b)
         blocks = matcher.get_matching_blocks()
         self.assertEqual(blocks, r)
 
@@ -33,7 +33,7 @@ class MatchersTests(unittest.TestCase):
         a = list('012a3456c789')
         b = list('0a3412b5678')
         r = [(0, 0, 1), (3, 1, 3), (6, 7, 2), (9, 9, 2), (12, 11, 0)]
-        matcher = matchers.SyncPointMyersSequenceMatcher(None, a, b)
+        matcher = myers.SyncPointMyersSequenceMatcher(None, a, b)
         blocks = matcher.get_matching_blocks()
         self.assertEqual(blocks, r)
 
@@ -41,7 +41,7 @@ class MatchersTests(unittest.TestCase):
         a = list('012a3456c789')
         b = list('0a3412b5678')
         r = [(0, 0, 1), (1, 4, 2), (6, 7, 2), (9, 9, 2), (12, 11, 0)]
-        matcher = matchers.SyncPointMyersSequenceMatcher(None, a, b, [(3, 6)])
+        matcher = myers.SyncPointMyersSequenceMatcher(None, a, b, [(3, 6)])
         blocks = matcher.get_matching_blocks()
         self.assertEqual(blocks, r)
 
@@ -49,7 +49,7 @@ class MatchersTests(unittest.TestCase):
         a = list('012a3456c789')
         b = list('02a341b5678')
         r = [(0, 0, 1), (2, 1, 1), (3, 2, 3), (9, 9, 2), (12, 11, 0)]
-        matcher = matchers.SyncPointMyersSequenceMatcher(
+        matcher = myers.SyncPointMyersSequenceMatcher(
             None, a, b, [(3, 2), (8, 6)])
         blocks = matcher.get_matching_blocks()
         self.assertEqual(blocks, r)


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]