diff --git a/cista/watching.py b/cista/watching.py index d0a4988..caaf97f 100644 --- a/cista/watching.py +++ b/cista/watching.py @@ -50,37 +50,42 @@ class State: begin, end = 0, len(self._listing) level = 0 isfile = 0 - while level < len(relpath.parts): - # Enter a subdirectory + + # Special case for root + if not relpath.parts: + return slice(begin, end) + + begin += 1 + for part in relpath.parts: level += 1 - begin += 1 - if level == len(relpath.parts): - isfile = relfile - name = relpath.parts[level - 1] - namesort = sortkey(name) - r = self._listing[begin] - assert r.level == level - # Iterate over items at this level - while ( - begin < end - and r.name != name - and r.isfile <= isfile - and sortkey(r.name) < namesort - ): - # Skip contents + found = False + + while begin < end: + entry = self._listing[begin] + + if entry.level < level: + break + + if entry.level == level: + if entry.name == part: + found = True + if level == len(relpath.parts): + isfile = relfile + else: + begin += 1 + break + cmp = entry.isfile - isfile or sortkey(entry.name) > sortkey(part) + if cmp > 0: + break + begin += 1 - while begin < end and self._listing[begin].level > level: - begin += 1 - # Not found? - if begin == end or self._listing[begin].level < level: - return slice(begin, begin) - r = self._listing[begin] - # Not found? - if begin == end or r.name != name: + + if not found: return slice(begin, begin) - # Found an item, now find its end - for end in range(begin + 1, len(self._listing)): - if self._listing[end].level <= level: + + # Found the starting point, now find the end of the slice + for end in range(begin + 1, len(self._listing) + 1): + if end == len(self._listing) or self._listing[end].level <= level: break return slice(begin, end) diff --git a/tests/test_watching.py b/tests/test_watching.py index 8e65faf..81301a7 100644 --- a/tests/test_watching.py +++ b/tests/test_watching.py @@ -1,7 +1,10 @@ +from pathlib import PurePosixPath + import msgspec +import pytest from cista.protocol import FileEntry, UpdateMessage, UpdDel, UpdIns, UpdKeep -from cista.watching import format_update +from cista.watching import State, format_update def decode(data: str): @@ -75,3 +78,59 @@ def test_longer_lists(): UpdIns(f(2, 7)), ] assert decode(format_update(old_list, new_list)) == expected + + +def sortkey(name): + # Define the sorting key for names here + return name.lower() + + +@pytest.fixture() +def state(): + entries = [ + FileEntry(0, "", "root", 0, 0, 0), + FileEntry(1, "bar", "bar", 0, 0, 0), + FileEntry(2, "baz", "bar/baz", 0, 0, 0), + FileEntry(1, "foo", "foo", 0, 0, 0), + FileEntry(1, "xxx", "xxx", 0, 0, 0), + FileEntry(2, "yyy", "xxx/yyy", 0, 0, 1), + ] + s = State() + s._listing = entries + return s + + +def test_existing_directory(state): + path = PurePosixPath("bar") + expected_slice = slice(1, 3) # Includes 'bar' and 'baz' + assert state._slice(path) == expected_slice + + +def test_existing_file(state): + path = PurePosixPath("xxx/yyy") + expected_slice = slice(5, 6) # Only includes 'yyy' + assert state._slice(path) == expected_slice + + +def test_nonexistent_directory(state): + path = PurePosixPath("zzz") + expected_slice = slice(6, 6) # 'zzz' would be inserted at end + assert state._slice(path) == expected_slice + + +def test_nonexistent_file(state): + path = (PurePosixPath("bar/mmm"), 1) + expected_slice = slice(3, 3) # A file would be inserted after 'baz' under 'bar' + assert state._slice(path) == expected_slice + + +def test_root_directory(state): + path = PurePosixPath() + expected_slice = slice(0, 6) # Entire tree + assert state._slice(path) == expected_slice + + +def test_directory_with_subdirs_and_files(state): + path = PurePosixPath("xxx") + expected_slice = slice(4, 6) # Includes 'xxx' and 'yyy' + assert state._slice(path) == expected_slice