Coverage for src/glotter_core/project.py: 100%
64 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 information, acronym schemes, and naming schemes."""
3from __future__ import annotations
5from dataclasses import dataclass, field
6from enum import Enum
7from typing import Any
10class NamingScheme(Enum):
11 """
12 Naming scheme for project filename. This defines how the project words are
13 converted to a filename.
15 :ivar hyphen: all words are separated by a hyphen (e.g., ``hello-world``)
16 :ivar underscore: all words are separated by underscore (e.g.,
17 ``hello_world``)
18 :ivar camel: the first word is lowercase, the remaining words are title case,
19 and all words are joined together (e.g., ``helloWorld``)
20 :ivar pascal: all words are title case and joined together (e.g.,
21 ``HelloWorld``)
22 :ivar lower: all words are lowercase and joined together (e.g.,
23 ``helloworld``)
24 """
26 hyphen = "hyphen"
27 underscore = "underscore"
28 camel = "camel"
29 pascal = "pascal"
30 lower = "lower"
33class AcronymScheme(Enum):
34 """
35 The acronym scheme overrides the naming scheme (:class:`NamingScheme`).
36 Each project word is checked against a list of acronyms. If there is a
37 match, then the acronym scheme applies.
39 :ivar lower: acronym word is lowercase
40 :ivar upper: acronym word is uppercase
41 :ivar two_letter_limit: acronym word is uppercase if the naming scheme is
42 ``camel`` or ``pascal``
43 """
45 lower = "lower"
46 upper = "upper"
47 two_letter_limit = "two_letter_limit"
50class CoreProjectMixin:
51 """
52 Mixin that can be used in a parent class to get to get project information.
54 The parent class must contain the following instance variables:
56 :ivar str words: Project words
57 :ivar list[str] acronyms: Project acronyms
58 :ivar AcronymScheme acronym_scheme: Acronym scheme
59 """
61 def get_project_name_by_scheme(self, naming: str | NamingScheme) -> str:
62 """
63 Get project name by on the specified naming scheme, the acronym scheme,
64 the project words, and the project acronyms
66 :param naming: Naming scheme
67 :return: Project name
68 :raises: :exc:`ValueError` if invalid naming scheme
69 """
71 try:
72 if not isinstance(naming, NamingScheme):
73 naming = NamingScheme[naming]
75 return {
76 NamingScheme.hyphen: self._as_hyphen,
77 NamingScheme.underscore: self._as_underscore,
78 NamingScheme.camel: self._as_camel,
79 NamingScheme.pascal: self._as_pascal,
80 NamingScheme.lower: self._as_lower,
81 }[naming]()
82 except KeyError as e:
83 raise ValueError(f'Unknown naming scheme "{naming}"') from e
85 @property
86 def display_name(self) -> str:
87 """
88 Get display name for the project. The project words are separated by
89 an spaces, subject to the acronym scheme -- e.g., ``hello world``).
91 :return: Display name
92 """
94 return self._as_display()
96 def _as_hyphen(self):
97 return "-".join(self._try_as_acronym(word, NamingScheme.hyphen) for word in self.words)
99 def _as_underscore(self):
100 return "_".join(self._try_as_acronym(word, NamingScheme.underscore) for word in self.words)
102 def _as_camel(self):
103 return self.words[0].lower() + "".join(
104 self._try_as_acronym(word.title(), NamingScheme.camel) for word in self.words[1:]
105 )
107 def _as_pascal(self):
108 return "".join(
109 self._try_as_acronym(word.title(), NamingScheme.pascal) for word in self.words
110 )
112 def _as_lower(self):
113 return "".join(word.lower() for word in self.words)
115 def _as_display(self):
116 return " ".join(
117 self._try_as_acronym(word.title(), NamingScheme.underscore) for word in self.words
118 )
120 def _is_acronym(self, word):
121 return word.upper() in self.acronyms
123 def _try_as_acronym(self, word, naming_scheme):
124 if self._is_acronym(word):
125 if self.acronym_scheme == AcronymScheme.upper:
126 return word.upper()
127 elif self.acronym_scheme == AcronymScheme.lower:
128 return word.lower()
129 elif len(word) <= 2 and naming_scheme in [
130 NamingScheme.camel,
131 NamingScheme.pascal,
132 ]:
133 return word.upper()
135 return word
138@dataclass(frozen=True)
139class CoreProject(CoreProjectMixin):
140 """
141 Project information. This class uses :class:`CoreProjectMixin` to implement
142 its functionality
144 :param project_dict: Project dictionary
145 :raises: :exc:`ValueError` if invalid acronym scheme
147 :ivar dict[str, Any] project_dict: Project dictionary
148 :ivar list[str] words: Project words
149 :ivar list[str] acronyms: Optional project acronyms. Default is no acronyms
150 :ivar AcronymScheme acronym_scheme: Optional project acronym scheme. Default is
151 :const:`AcronymScheme.two_letter_limit`
152 """
154 words: list[str]
155 acronyms: list[str]
156 acronym_scheme: AcronymScheme
157 project_dict: dict[str, Any] = field(repr=False)
159 def __init__(self, project_dict: dict[str, Any]):
160 object.__setattr__(self, "project_dict", project_dict)
161 object.__setattr__(self, "words", project_dict["words"])
162 object.__setattr__(
163 self, "acronyms", [acronym.upper() for acronym in project_dict.get("acronyms", [])]
164 )
165 acronym_scheme = project_dict.get("acronym_scheme", "two_letter_limit")
166 try:
167 object.__setattr__(self, "acronym_scheme", AcronymScheme[acronym_scheme])
168 except KeyError as e:
169 raise ValueError(f'Unknown acronym scheme: "{acronym_scheme}"') from e
172__all__ = ["AcronymScheme", "CoreProject", "CoreProjectMixin", "NamingScheme"]