Skip to content

Commit 309711f

Browse files
authored
Add tree method to display tree-like structure of the filesystem
>>> fs.tree(path='/start/folder', display_size=True) /start/folder ├── folder1 │ ├── file1.txt (1.234MB) │ └── file2.txt (0.567MB) └── folder2 └── file3.txt (2.345MB)
1 parent 4cb98ab commit 309711f

File tree

1 file changed

+73
-0
lines changed

1 file changed

+73
-0
lines changed

fsspec/spec.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1567,6 +1567,79 @@ def modified(self, path):
15671567
"""Return the modified timestamp of a file as a datetime.datetime"""
15681568
raise NotImplementedError
15691569

1570+
def tree(
1571+
self,
1572+
path: str = '/',
1573+
recursion_limit: int = 2,
1574+
max_display: int = 25,
1575+
display_size: bool = False,
1576+
prefix: str = "",
1577+
is_last: bool = True,
1578+
first: bool = True
1579+
):
1580+
"""
1581+
Display a tree-like structure of the filesystem starting from the given path.
1582+
1583+
Parameters
1584+
----------
1585+
path: Root path to start traversal from
1586+
recursion_limit: Maximum depth of directory traversal
1587+
max_display: Maximum number of items to display per directory
1588+
display_size: Whether to display file sizes
1589+
prefix: Current line prefix for visual tree structure
1590+
is_last: Whether current item is last in its level
1591+
first: Whether this is the first call (displays root path)
1592+
1593+
Example
1594+
-------
1595+
>>> fs.tree(path='/start/folder', display_size=True)
1596+
1597+
/start/folder
1598+
├── folder1
1599+
│ ├── file1.txt (1.234MB)
1600+
│ └── file2.txt (0.567MB)
1601+
└── folder2
1602+
└── file3.txt (2.345MB)
1603+
"""
1604+
if first:
1605+
print(path)
1606+
if recursion_limit:
1607+
try:
1608+
contents = self.ls(path, detail=True)
1609+
contents.sort(key=lambda x: (not x.get('type') == 'directory', x.get('name', '')))
1610+
1611+
if max_display is not None and len(contents) > max_display:
1612+
displayed_contents = contents[:max_display]
1613+
remaining_count = len(contents) - max_display
1614+
else:
1615+
displayed_contents = contents
1616+
remaining_count = 0
1617+
1618+
for i, item in enumerate(displayed_contents):
1619+
is_last_item = (i == len(displayed_contents) - 1) and (remaining_count == 0)
1620+
1621+
branch = "└── " if is_last_item else "├── "
1622+
new_prefix = prefix + (" " if is_last_item else "│ ")
1623+
1624+
name = os.path.basename(item.get('name', ''))
1625+
size = f" ({item.get('size', 0) / 2**20:.3f}Mb)" if display_size and item.get('type') == 'file' else ""
1626+
1627+
print(f"{prefix}{branch}{name}{size}")
1628+
1629+
if item.get('type') == 'directory' and recursion_limit > 0:
1630+
self.tree(item.get('name', ''), recursion_limit - 1, max_display, new_prefix, is_last_item, display_size=display_size, first=False)
1631+
1632+
if remaining_count > 0:
1633+
more_message = f"{remaining_count} more item(s) not displayed."
1634+
print(f"{prefix}{'└── ' if is_last else '├── '}{more_message}")
1635+
1636+
except FileNotFoundError:
1637+
print(f"{prefix}Error: Path not found - {path}")
1638+
except PermissionError:
1639+
print(f"{prefix}Error: Permission denied - {path}")
1640+
except Exception as e:
1641+
print(f"{prefix}Unexpected error: {str(e)}")
1642+
15701643
# ------------------------------------------------------------------------
15711644
# Aliases
15721645

0 commit comments

Comments
 (0)