138 lines
3.9 KiB
Python
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
|