Coverage for glotter/project.py: 100%

75 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2025-10-08 17:57 +0000

1from enum import Enum, auto 

2from typing import Annotated, ClassVar, Dict, List, Optional 

3 

4from pydantic import BaseModel, Field, ValidationInfo, field_validator 

5 

6from glotter.auto_gen_test import AutoGenTest, AutoGenUseTests 

7from glotter.errors import raise_simple_validation_error, validate_str_list 

8 

9 

10class NamingScheme(Enum): 

11 hyphen = auto() 

12 underscore = auto() 

13 camel = auto() 

14 pascal = auto() 

15 lower = auto() 

16 

17 

18class AcronymScheme(Enum): 

19 lower = "lower" 

20 upper = "upper" 

21 two_letter_limit = "two_letter_limit" 

22 

23 

24class Project(BaseModel): 

25 VALID_REGEX: ClassVar[str] = "^[0-9a-zA-Z]+$" 

26 

27 words: Annotated[ 

28 List[Annotated[str, Field(min_length=1, pattern=VALID_REGEX, strict=True)]], 

29 Field(min_length=1, strict=True), 

30 ] 

31 requires_parameters: bool = False 

32 acronyms: List[Annotated[str, Field(min_length=1, pattern=VALID_REGEX, strict=True)]] = [] 

33 acronym_scheme: AcronymScheme = AcronymScheme.two_letter_limit 

34 use_tests: Optional[AutoGenUseTests] = None 

35 tests: Dict[str, AutoGenTest] = {} 

36 

37 @field_validator("acronyms", mode="before") 

38 @classmethod 

39 def get_acronym(cls, values): 

40 validate_str_list(cls, values) 

41 return [value.upper() for value in values] 

42 

43 @field_validator("tests", mode="before") 

44 @classmethod 

45 def get_tests(cls, value, info: ValidationInfo): 

46 if not isinstance(value, dict) or not all( 

47 isinstance(test, dict) for test in value.values() 

48 ): 

49 return value 

50 

51 if info.data.get("use_tests"): 

52 raise_simple_validation_error( 

53 cls, '"tests" and "use_tests" items are mutually exclusive', {"use_tests": "..."} 

54 ) 

55 

56 return { 

57 test_name: { 

58 **test, 

59 "requires_parameters": info.data.get("requires_parameters") or False, 

60 "name": test_name, 

61 } 

62 for test_name, test in value.items() 

63 } 

64 

65 def set_tests(self, project: "Project"): 

66 """ 

67 If there is a "use_tests" item, then set the specified tests, renaming them 

68 according to the "use_tests" item. The "use_tests" item is then removed 

69 

70 :params tests: Project with tests to use 

71 """ 

72 

73 if self.use_tests: 

74 self.tests = {} 

75 for test_name_, test in project.tests.items(): 

76 test_name = test_name_.replace(self.use_tests.search, self.use_tests.replace) 

77 self.tests[test_name] = AutoGenTest( 

78 **test.model_dump(exclude={"name"}), name=test_name 

79 ) 

80 

81 self.requires_parameters = project.requires_parameters 

82 self.use_tests = None 

83 

84 @property 

85 def display_name(self): 

86 return self._as_display() 

87 

88 def get_project_name_by_scheme(self, naming): 

89 """ 

90 gets a project name for a specific naming scheme 

91 

92 :param naming: the naming scheme 

93 :return: the project type formatted by the directory's naming scheme 

94 """ 

95 try: 

96 return { 

97 NamingScheme.hyphen: self._as_hyphen(), 

98 NamingScheme.underscore: self._as_underscore(), 

99 NamingScheme.camel: self._as_camel(), 

100 NamingScheme.pascal: self._as_pascal(), 

101 NamingScheme.lower: self._as_lower(), 

102 }[naming] 

103 except KeyError as e: 

104 raise KeyError(f'Unknown naming scheme "{naming}"') from e 

105 

106 def _as_hyphen(self): 

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

108 

109 def _as_underscore(self): 

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

111 

112 def _as_camel(self): 

113 return self.words[0].lower() + "".join( 

114 self._try_as_acronym(word.title(), NamingScheme.camel) for word in self.words[1:] 

115 ) 

116 

117 def _as_pascal(self): 

118 return "".join( 

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

120 ) 

121 

122 def _as_lower(self): 

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

124 

125 def _as_display(self): 

126 return " ".join( 

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

128 ) 

129 

130 def _is_acronym(self, word): 

131 return word.upper() in self.acronyms 

132 

133 def _try_as_acronym(self, word, naming_scheme): 

134 if self._is_acronym(word): 

135 if self.acronym_scheme == AcronymScheme.upper: 

136 return word.upper() 

137 elif self.acronym_scheme == AcronymScheme.lower: 

138 return word.lower() 

139 elif len(word) <= 2 and naming_scheme in [ 

140 NamingScheme.camel, 

141 NamingScheme.pascal, 

142 ]: 

143 return word.upper() 

144 

145 return word