Source code for src.utils
import logging
from collections.abc import Callable, Iterable
from pathlib import Path
from typing import Optional
from git.repo import Repo
logger: logging.Logger = logging.getLogger(__name__)
# shamelessly stolen from
# https://stackoverflow.com/questions/1175208/elegant-python-function-to-convert-camelcase-to-snake-case/44969381#44969381
[docs]
def camel_to_snake(s: str) -> str:
"""
Converts a string from CamelCase to snake_case. Does not handle many of the
special cases.
"""
return "".join(["_" + c.lower() if c.isupper() else c for c in s]).lstrip(
"_"
)
[docs]
def dir_list(
repo: Repo, dir_predicate: Callable[[str], bool]
) -> Iterable[Path]:
"""
Searches the project for directories that match the `dir_predicate`. If a
directory matches its subtree is not explored for more matching directories.
:param repo: The local checkout of the project.
:param dir_predicate: Function that receives the directory name and returns
True iff the spanned subtree should be yielded.
"""
trees: Optional[list[Path]] = None
try:
logger.debug(f"Assuming fs repo base at {repo.working_dir}")
trees = [Path(repo.working_dir)]
except ValueError as E:
logger.error(f"Unable to obtain tree for {repo}: {E}")
while trees:
tree: Path = trees.pop()
for f in tree.iterdir():
if not f.is_dir():
continue
if dir_predicate(f.name):
yield f
trees.append(f)
[docs]
def file_list(
repo: Repo,
file_name_filter: Callable[[str], bool] = lambda _: False,
path_component_filter: Callable[[str], bool] = lambda _: False,
root: Optional[Path] = None,
recursive: bool = True,
) -> Iterable[Path]:
"""
Generates a list of all files in a project. Optionally, the yielded
files can be filtered based on path components or file name. The function
can also be restricted to a subtree.
:param repo: The local checkout of the project.
:param file_name_filter: Function that receives a file name and returns
True iff the file should be skipped.
:param path_component_filter: Function that receives the name of a directory
and returns True iff the whole directory should be skipped.
:param root: Optionally, search only the subtree rooted at root
:param recursive: Iterate through folders recursively
:return: Iterator over all files in the project
"""
trees: Optional[list[Path]] = None
try:
logger.debug(f"Assuming fs repo base at {repo.working_dir}")
trees = [root if root else Path(str(repo.working_dir))]
except ValueError as E:
logger.error(f"Unable to obtain tree for {repo}: {E}")
while trees:
tree: Path = trees.pop()
for f in tree.iterdir():
if f.is_symlink():
continue
if f.is_file() and file_name_filter(f.name):
logger.debug(f"Skipping filtered file {f.as_posix()}")
continue
if f.is_dir() and recursive:
if path_component_filter(f.name):
logger.debug(f"Skipping filtered subtree {f.as_posix()}")
else:
trees.append(f)
continue
if not f.is_file():
continue
logger.debug(f"Yielding {f.as_posix()}")
yield f
[docs]
def get_publiccode_cfg(repo: Repo) -> Path | None:
"""
Tries to find the OpenCoDE configuration file in the repository.
:param repo: The local checkout of the project.
:return: path of the file or None if it does not exist.
"""
files_in_repo_root = {f.name: f for f in file_list(repo, recursive=False)}
for path in ("publiccode.yml", "publiccode.yaml"):
if path in files_in_repo_root:
return files_in_repo_root[path]
return None