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

1"""Project information, acronym schemes, and naming schemes.""" 

2 

3from __future__ import annotations 

4 

5from dataclasses import dataclass, field 

6from enum import Enum 

7from typing import Any 

8 

9 

10class NamingScheme(Enum): 

11 """ 

12 Naming scheme for project filename. This defines how the project words are 

13 converted to a filename. 

14 

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 """ 

25 

26 hyphen = "hyphen" 

27 underscore = "underscore" 

28 camel = "camel" 

29 pascal = "pascal" 

30 lower = "lower" 

31 

32 

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. 

38 

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 """ 

44 

45 lower = "lower" 

46 upper = "upper" 

47 two_letter_limit = "two_letter_limit" 

48 

49 

50class CoreProjectMixin: 

51 """ 

52 Mixin that can be used in a parent class to get to get project information. 

53 

54 The parent class must contain the following instance variables: 

55 

56 :ivar str words: Project words 

57 :ivar list[str] acronyms: Project acronyms 

58 :ivar AcronymScheme acronym_scheme: Acronym scheme 

59 """ 

60 

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 

65 

66 :param naming: Naming scheme 

67 :return: Project name 

68 :raises: :exc:`ValueError` if invalid naming scheme 

69 """ 

70 

71 try: 

72 if not isinstance(naming, NamingScheme): 

73 naming = NamingScheme[naming] 

74 

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 

84 

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``). 

90 

91 :return: Display name 

92 """ 

93 

94 return self._as_display() 

95 

96 def _as_hyphen(self): 

97 return "-".join(self._try_as_acronym(word, NamingScheme.hyphen) for word in self.words) 

98 

99 def _as_underscore(self): 

100 return "_".join(self._try_as_acronym(word, NamingScheme.underscore) for word in self.words) 

101 

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 ) 

106 

107 def _as_pascal(self): 

108 return "".join( 

109 self._try_as_acronym(word.title(), NamingScheme.pascal) for word in self.words 

110 ) 

111 

112 def _as_lower(self): 

113 return "".join(word.lower() for word in self.words) 

114 

115 def _as_display(self): 

116 return " ".join( 

117 self._try_as_acronym(word.title(), NamingScheme.underscore) for word in self.words 

118 ) 

119 

120 def _is_acronym(self, word): 

121 return word.upper() in self.acronyms 

122 

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() 

134 

135 return word 

136 

137 

138@dataclass(frozen=True) 

139class CoreProject(CoreProjectMixin): 

140 """ 

141 Project information. This class uses :class:`CoreProjectMixin` to implement 

142 its functionality 

143 

144 :param project_dict: Project dictionary 

145 :raises: :exc:`ValueError` if invalid acronym scheme 

146 

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 """ 

153 

154 words: list[str] 

155 acronyms: list[str] 

156 acronym_scheme: AcronymScheme 

157 project_dict: dict[str, Any] = field(repr=False) 

158 

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 

170 

171 

172__all__ = ["AcronymScheme", "CoreProject", "CoreProjectMixin", "NamingScheme"]