"""Source information"""
import os
from dataclasses import dataclass, field
from pathlib import Path
import yaml
from glotter_core.project import CoreProjectMixin, NamingScheme
from glotter_core.testinfo import TestInfo
[docs]
@dataclass(frozen=True)
class CoreSource:
"""Metadata about a source file
:param filename: filename including extension
:param language: the language of the source
:param path: path to the file excluding name
:param str test_info: a string in yaml format containing testinfo for a directory
:param project_type: name of project for this source
:ivar filename: filename including extension
:ivar language: the language of the source
:ivar path: path to the file excluding name
:ivar TestInfo test_info: TestInfo object
:param project_type: name of project for this source
"""
filename: str
language: str
path: str
test_info: str = field(repr=False)
project_type: str
def __post_init__(self) -> None:
object.__setattr__(self, "test_info", TestInfo.from_string(self.test_info, self))
@property
def full_path(self) -> str:
"""Returns the full path to the source including filename and extension"""
return str(Path(self.path) / self.filename)
@property
def name(self) -> str:
"""Returns the name of the source excluding the extension"""
return self.filename.split(".")[0]
@property
def extension(self) -> str:
"""Returns the extension of the source"""
return "".join(Path(self.filename).suffixes)
[docs]
@dataclass
class CoreLanguage:
"""
Information about a language
:ivar sources: list of source objects
:ivar test_info: TestInfo object
:ivar path: Path to TestInfo object file
"""
sources: list[CoreSource]
test_info: TestInfo
test_info_path: Path
[docs]
@dataclass
class CoreSourceCategories:
"""
Categories for sources
:ivar testable_by_project: dictionary whose key is the project type and
whose value is a list of testable source object
:ivar by_language: dictionary whose key is the language and whose
value is a CoreLanguage object
:ivar bad_sources: list of filenames that do not belong to a project
"""
testable_by_project: dict[str, list[CoreSource]] = field(default_factory=dict)
by_language: dict[str, list[CoreLanguage]] = field(default_factory=dict)
bad_sources: list[str] = field(default_factory=list)
_IGNORED_FILENAMES = {"untestable.yml", "testinfo.yml", "README.md"}
[docs]
def categorize_sources(
path: str, projects: dict[str, CoreProjectMixin], source_cls: type
) -> CoreSourceCategories:
"""
Categorize sources
:param path: path to source directory
:param projects: dictionary whose key is a project type and whose value is a
CoreProjectMixin object
:param source_cls: source object class
:return: CoreSourceCategories object containing information of the source
categories
"""
categories = CoreSourceCategories()
categories.testable_by_project = {k: [] for k in projects}
orig_path = Path(path).resolve()
for root, _, files in os.walk(path):
current_path = Path(root).resolve()
test_info_string = ""
test_info_filename = ""
if "testinfo.yml" in files:
test_info_filename = "testinfo.yml"
test_info_string = Path(current_path, test_info_filename).read_text(encoding="utf-8")
elif "untestable.yml" in files:
test_info_filename = "untestable.yml"
test_info_string = _convert_untestable_to_testinfo(current_path, files, projects)
if test_info_string:
language = current_path.name
test_info = TestInfo.from_dict(yaml.safe_load(test_info_string), language)
folder_info = test_info.file_info
folder_project_names = folder_info.get_project_mappings(
projects, include_extension=True
)
sources = []
test_info_path = Path(current_path, test_info_filename)
for project_type, project_name in folder_project_names.items():
if project_name in files:
source = source_cls(
filename=project_name,
language=language,
path=str(current_path),
test_info=test_info_string,
project_type=project_type,
)
sources.append(source)
if source.test_info.is_testable:
categories.testable_by_project[project_type].append(source)
categories.by_language[language] = CoreLanguage(sources, test_info, test_info_path)
invalid_filenames = set(files) - (
set(folder_project_names.values()) | _IGNORED_FILENAMES
)
categories.bad_sources += [
str(current_path.relative_to(orig_path) / filename)
for filename in invalid_filenames
]
return categories
def _convert_untestable_to_testinfo(
current_path: Path, files: list[str], projects: dict[str, CoreProjectMixin]
) -> str:
with Path(current_path, "untestable.yml").open(encoding="utf-8") as f:
untestable_data = yaml.safe_load(f)
notes = untestable_data[0]["reason"]
for filename in files:
if filename in _IGNORED_FILENAMES:
continue
base_filename = filename.split(".")[0]
extension = "".join(Path(filename).suffixes)
project_type = base_filename.lower().replace("-", "").replace("_", "")
if project_type in projects and len(projects[project_type].words) > 1:
for naming_scheme in NamingScheme:
expected_filename = (
projects[project_type].get_project_name_by_scheme(naming_scheme) + extension
)
if filename == expected_filename:
test_info_dict = {
"folder": {
"extension": extension,
"naming": naming_scheme.value,
},
"notes": [notes],
}
return yaml.dump(test_info_dict, sort_keys=False)
return ""
__all__ = ["CoreLanguage", "CoreSource", "CoreSourceCategories", "categorize_sources"]