Coverage for src/glotter_core/settings.py: 100%
61 statements
« prev ^ index » next coverage.py v7.10.7, created at 2026-03-03 02:09 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2026-03-03 02:09 +0000
1"""Project settings"""
3from __future__ import annotations
5import os
6from dataclasses import dataclass, field
7from pathlib import Path
8from typing import Any
9from warnings import warn
11import yaml
13from .project import AcronymScheme, CoreProject
16@dataclass(frozen=True, init=False)
17class CoreSettings:
18 """Global project settings
20 :raises: :exc:`ValueError` if invalid settings
22 :ivar str project_root: Root directory of project
23 :ivar src source_root: Root directory for source files
24 :ivar AcronymScheme acronym_scheme: Optional project acronym scheme.
25 Default is :const:`AcronymScheme.two_letter_limit`
26 :ivar projects: Dictionary whose key is the project name and whose value
27 is the project information
28 """
30 project_root: str = ""
31 acronym_scheme: AcronymScheme = AcronymScheme.two_letter_limit
32 source_root: str = ""
33 projects: dict[str | CoreProject] = field(default=dict)
35 def __init__(self) -> None:
36 object.__setattr__(self, "project_root", str(Path.cwd()))
37 parser = CoreSettingsParser(self.project_root)
38 self._set_global_settings(parser.yml.get("settings", {}))
39 self._set_projects(parser.yml.get("projects", {}))
41 def _set_global_settings(self, settings_item: Any) -> None:
42 if not isinstance(settings_item, dict):
43 raise ValueError("settings does not contain a dict")
45 acronym_scheme = settings_item.get("acronym_scheme", "two_letter_limit")
46 try:
47 object.__setattr__(self, "acronym_scheme", AcronymScheme[acronym_scheme])
48 except KeyError:
49 raise ValueError(f'Unknown acronym scheme: "{acronym_scheme}"')
51 source_root = settings_item.get("source_root") or self.project_root
52 object.__setattr__(self, "source_root", str(Path(source_root).resolve()))
54 def _set_projects(self, projects_item: dict[str, Any]) -> None:
55 if not isinstance(projects_item, dict):
56 raise ValueError("projects does not contain a dict")
58 projects = {name: CoreProject(project) for name, project in projects_item.items()}
59 object.__setattr__(self, "projects", projects)
62@dataclass(frozen=True, init=False)
63class CoreSettingsParser:
64 """Parse the settings file (``.glotter.yml``)
66 :param project_root: Root directory of project
67 :raises: :exc:`ValueError` if setting file does not contain a dictionary
69 :ivar str project_root: Root directory of project
70 :ivar str | None yml_path: Path to ``.glotter.yml`` file
71 :ivar dict[str, Any] yml: Contents of ``.glotter.yml`` file
72 """
74 project_root: str
75 yml_path: str | None = None
76 yml: dict[str, Any] = field(default_factory=dict, repr=False)
78 def __init__(self, project_root):
79 object.__setattr__(self, "project_root", project_root)
80 object.__setattr__(self, "yml_path", self._locate_yml())
82 yml = None
83 if self.yml_path is not None:
84 yml = self._parse_yml()
85 else:
86 object.__setattr__(self, "yml_path", self.project_root)
87 warn(f'.glotter.yml not found in directory "{self.project_root}"')
89 if yml is None:
90 yml = {}
92 if not isinstance(yml, dict):
93 raise ValueError(".glotter.yml does not contain a dict")
95 object.__setattr__(self, "yml", yml)
97 def _parse_yml(self) -> Any:
98 contents = Path(self.yml_path).read_text(encoding="utf-8")
99 return yaml.safe_load(contents)
101 def _locate_yml(self) -> str | None:
102 for root, _, files in os.walk(self.project_root):
103 if ".glotter.yml" in files:
104 return str((Path(root) / ".glotter.yml").resolve())
106 return None
109__all__ = ["CoreSettings", "CoreSettingsParser"]