Commits

jacobmason committed ee90c8f Merge

merge with DasIch

Comments (0)

Files changed (2)

sphinx/versioning.py

 from operator import itemgetter
 from collections import defaultdict
 from itertools import product
+try:
+    from itertools import izip_longest as zip_longest
+except ImportError:
+    from itertools import zip_longest
 
 from sphinx.util import PeekableIterator
 
+# anything below that ratio is considered equal/changed
+VERSIONING_RATIO = 65
+
 def add_uids(doctree, condition):
     """
     Adds a unique id to every node in the `doctree` which matches the condition
     :param condition:
         A callable which returns either ``True`` or ``False`` for a given node.
     """
-    old_nodes = old.traverse(condition)
-    new_nodes = new.traverse(condition)
+    old_iter = old.traverse(condition)
+    new_iter = new.traverse(condition)
+    old_nodes = []
+    new_nodes = []
     ratios = defaultdict(list)
     seen = set()
-    for old_node, new_node in product(old_nodes, new_nodes):
-        if new_node in seen:
+    # compare the nodes each doctree in order
+    for old_node, new_node in zip_longest(old_iter, new_iter):
+        if old_node is None:
+            new_nodes.append(new_node)
+            continue
+        if new_node is None:
+            old_nodes.append(old_node)
             continue
         ratio = get_ratio(old_node.rawsource, new_node.rawsource)
         if ratio == 0:
             seen.add(new_node)
         else:
             ratios[old_node, new_node] = ratio
+            old_nodes.append(old_node)
+            new_nodes.append(new_node)
+    # calculate the ratios for each unequal pair of nodes, should we stumble
+    # on a pair which is equal we set the uid and add it to the seen ones
+    for old_node, new_node in product(old_nodes, new_nodes):
+        if new_node in seen or (old_node, new_node) in ratios:
+            continue
+        ratio = get_ratio(old_node.rawsource, new_node.rawsource)
+        if ratio == 0:
+            new_node.uid = old_node.uid
+            seen.add(new_node)
+        else:
+            ratios[old_node, new_node] = ratio
+    # choose the old node with the best ratio for each new node and set the uid
+    # as long as the ratio is under a certain value, in which case we consider
+    # them not changed but different
     ratios = sorted(ratios.iteritems(), key=itemgetter(1))
     for (old_node, new_node), ratio in ratios:
         if new_node in seen:
             continue
         else:
             seen.add(new_node)
-        if ratio < 65:
+        if ratio < VERSIONING_RATIO:
             new_node.uid = old_node.uid
         else:
             new_node.uid = uuid4().hex
             yield new_node
+    # create new uuids for any new node we left out earlier, this happens
+    # if one or more nodes are simply added.
+    for new_node in set(new_nodes) - seen:
+        new_node.uid = uuid4().hex
+        yield new_node
 
 def get_ratio(old, new):
     """
     Returns a "similiarity ratio" representing the similarity between the two
     strings where 0 is equal and anything above less than equal.
     """
+    if not all([old, new]):
+        return VERSIONING_RATIO
     return levenshtein_distance(old, new) / (len(old) / 100.0)
 
 def levenshtein_distance(a, b):

tests/test_versioning.py

 from docutils.parsers.rst.directives.html import MetaBody
 
 from sphinx import addnodes
-from sphinx.versioning import add_uids, merge_doctrees
+from sphinx.versioning import add_uids, merge_doctrees, get_ratio
 
 def setup_module():
     global app, original, original_uids
 def is_paragraph(node):
     return node.__class__.__name__ == 'paragraph'
 
+def test_get_ratio():
+    assert get_ratio('', 'a')
+    assert get_ratio('a', '')
+
 def test_add_uids():
     assert len(original_uids) == 3