workflows/unit_tests/tools/directory_viewer.py
2024-01-17 15:34:22 +08:00

138 lines
3.9 KiB
Python

from abc import ABC, abstractmethod
from collections import defaultdict
from pathlib import Path
from typing import Callable, List, Optional
from tools.file_util import is_not_hidden, is_source_code
from tools.git_util import git_file_of_interest_filter
class DirectoryViewer(ABC):
def __init__(
self,
root_path: str,
criteria: Optional[Callable] = None,
):
self.root_path = Path(root_path)
self.criteria = criteria
self._items = None
self._max_depth = None
@property
def items(self) -> List[Path]:
"""
Get all items that pass the criteria under the root path.
"""
if self._items is not None:
return self._items
self._items = []
# Also compute the max depth when getting items
max_depth = 0
for filepath in self.root_path.rglob("*"):
relpath = filepath.relative_to(self.root_path)
if self.criteria and not self.criteria(relpath):
continue
depth = len(relpath.parts)
max_depth = depth if depth > max_depth else max_depth
self._items.append(filepath)
self._items.sort(key=lambda s: str(s).lower())
self._max_depth = max_depth
return self._items
@property
def max_depth(self) -> int:
# trigger the computation of max_depth
_ = self.items
return self._max_depth
@abstractmethod
def visualize(
self,
depth: Optional[int] = None,
page_size: Optional[int] = None,
page: int = 0,
) -> str:
pass
def mk_repo_file_criteria(repo_path: str) -> Callable[[Path], bool]:
is_git_interest = git_file_of_interest_filter(repo_path)
def criteria(filepath: Path) -> bool:
return (
is_not_hidden(filepath) and is_git_interest(filepath) and is_source_code(str(filepath))
)
return criteria
class ListViewer(DirectoryViewer):
def _get_items_to_show(
self,
depth: Optional[int] = None,
page_size: Optional[int] = None,
page: int = 0,
):
"""
Get items with depth to show on the page.
depth: 1-based.
page: 0-based.
"""
# Get items whose depth is less than or equal to the given depth.
items_ = (
self.items
if depth is None
else [i for i in self.items if len(i.relative_to(self.root_path).parts) <= depth]
)
# Get items to show on the page if page size is given.
items_ = items_ if page_size is None else items_[page * page_size : (page + 1) * page_size]
return items_
def visualize(
self,
depth: Optional[int] = None,
page_size: Optional[int] = None,
page: int = 0,
) -> str:
"""
Visualize the directory structure with the given depth and page size.
Args:
depth: 1-based depth of the dir structure to show. If None, show the entire dir.
page_size: The number of items to show on the page. if None, show all items.
page: The page number to show. 0-based.
"""
items_to_show = self._get_items_to_show(depth=depth, page_size=page_size, page=page)
dir_files = defaultdict(list)
for item in items_to_show:
relpath = item.relative_to(self.root_path)
if item.is_file():
dir_path = relpath.parent
dir_files[dir_path].append(relpath)
elif item.is_dir():
# add a key to indicate there is a dir
dir_files[relpath]
keys = sorted(list(dir_files.keys()))
visualization = ""
for k in keys:
visualization += f"\n`{k}`:\n"
for item in dir_files[k]:
visualization += f"- `{item.name}`\n"
return visualization
class TreeViewer(DirectoryViewer):
pass