[meld] misc: Update path shortening logic for Windows paths (#203)



commit e2cc43a4e6a13d66a39b0873aac9146a3e1681d5
Author: Kai Willadsen <kai willadsen gmail com>
Date:   Sun Jul 15 10:26:50 2018 +1000

    misc: Update path shortening logic for Windows paths (#203)

 meld/misc.py      | 45 ++++++++++++++++++++++++---------------------
 test/test_misc.py | 23 +++++++++++++++++++++++
 2 files changed, 47 insertions(+), 21 deletions(-)
---
diff --git a/meld/misc.py b/meld/misc.py
index 49f77dc4..1d700d09 100644
--- a/meld/misc.py
+++ b/meld/misc.py
@@ -25,6 +25,7 @@ import os
 import re
 import shutil
 import subprocess
+from pathlib import PurePath
 
 from gi.repository import Gdk
 from gi.repository import GLib
@@ -293,30 +294,32 @@ def all_same(iterable):
 
 
 def shorten_names(*names):
-    """Remove redunant parts of a list of names (e.g. /tmp/foo{1,2} -> foo{1,2}
+    """Remove common parts of a list of paths
+
+    For example, `('/tmp/foo1', '/tmp/foo2')` would be summarised as
+    `('foo1', 'foo2')`. Paths that share a basename are distinguished
+    by prepending an indicator, e.g., `('/a/b/c', '/a/d/c')` would be
+    summarised to `['[b] c', '[d] c']`.
     """
-    # TODO: Update for different path separators and URIs
-    prefix = os.path.commonprefix(names)
-    prefixslash = prefix.rfind("/") + 1
 
-    names = [n[prefixslash:] for n in names]
-    paths = [n.split("/") for n in names]
+    paths = [PurePath(n) for n in names]
+
+    # Identify the longest common path among the list of path
+    common = set(paths[0].parents)
+    common = common.intersection(*(p.parents for p in paths))
+    common_parent = sorted(common, key=lambda p: -len(p.parts))[0]
+
+    paths = [p.relative_to(common_parent) for p in paths]
+    basenames = [p.name for p in paths]
+
+    if all_same(basenames):
+        def firstpart(path: PurePath):
+            if len(path.parts) > 1 and path.parts[0]:
+                return "[%s] " % path.parts[0]
+            else:
+                return ""
+        return [firstpart(p) + p.name for p in paths]
 
-    try:
-        basenames = [p[-1] for p in paths]
-    except IndexError:
-        pass
-    else:
-        if all_same(basenames):
-            def firstpart(alist):
-                if len(alist) > 1 and alist[0]:
-                    return "[%s] " % alist[0]
-                else:
-                    return ""
-            roots = [firstpart(p) for p in paths]
-            base = basenames[0].strip()
-            return [r + base for r in roots]
-    # no common path. empty names get changed to "[None]"
     return [name or _("[None]") for name in basenames]
 
 
diff --git a/test/test_misc.py b/test/test_misc.py
index 334be727..47cc900a 100644
--- a/test/test_misc.py
+++ b/test/test_misc.py
@@ -1,4 +1,6 @@
 
+from unittest import mock
+
 import pytest
 from meld.misc import all_same, calc_syncpoint, merge_intervals
 
@@ -73,3 +75,24 @@ def test_calc_syncpoint(value, page_size, lower, upper, expected):
 ])
 def test_all_same(lst, expected):
     assert all_same(lst) == expected
+
+
+@pytest.mark.parametrize("os_name, paths, expected", [
+    ('posix', ['/tmp/foo1', '/tmp/foo2'], ['foo1', 'foo2']),
+    ('posix', ['/tmp/foo1', '/tmp/foo2', '/tmp/foo3'], ['foo1', 'foo2', 'foo3']),
+    ('posix', ['/tmp/bar/foo1', '/tmp/woo/foo2'], ['foo1', 'foo2']),
+    ('posix', ['/tmp/bar/foo1', '/tmp/woo/foo1'], ['[bar] foo1', '[woo] foo1']),
+    ('posix', ['/tmp/bar/foo1', '/tmp/woo/foo1', '/tmp/ree/foo1'], ['[bar] foo1', '[woo] foo1', '[ree] 
foo1']),
+    ('posix', ['/tmp/bar/deep/deep', '/tmp/bar/shallow'], ['deep', 'shallow']),
+    ('posix', ['/tmp/bar/deep/deep/foo1', '/tmp/bar/shallow/foo1'], ['[deep] foo1', '[shallow] foo1']),
+    # This case doesn't actually make much sense, so it's not that bad
+    # that our output is... somewhat unclear.
+    ('posix', ['/tmp/bar/subdir/subsub', '/tmp/bar/'], ['subsub', 'bar']),
+    ('nt', ['C:\\Users\\hmm\\bar', 'C:\\Users\\hmm\\foo'], ['bar', 'foo']),
+    ('nt', ['C:\\Users\\bar\\hmm', 'C:\\Users\\foo\\hmm'], ['[bar] hmm', '[foo] hmm']),
+])
+def test_shorten_names(os_name, paths, expected):
+    from meld.misc import shorten_names
+
+    with mock.patch('os.name', os_name):
+        assert shorten_names(*paths) == expected


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