Coverage for sel_tools/utils/args.py: 100%

80 statements  

« prev     ^ index     » next       coverage.py v7.6.4, created at 2024-11-04 21:22 +0000

1"""Argparse helper module.""" 

2 

3import copy 

4from argparse import Action, ArgumentDefaultsHelpFormatter, ArgumentParser, FileType 

5from datetime import date 

6from pathlib import Path 

7from typing import Any 

8 

9from sel_tools.config import REPO_DIR 

10 

11 

12def dir_path(path_string: str) -> Path: 

13 """Argparse type check if path is a directory.""" 

14 if Path(path_string).is_dir(): 

15 return Path(path_string) 

16 raise NotADirectoryError(path_string) 

17 

18 

19def file_path(path_string: str) -> Path: 

20 """Argparse type check if path is a file.""" 

21 if Path(path_string).is_file(): 

22 return Path(path_string) 

23 raise FileNotFoundError(path_string) 

24 

25 

26class DateAction(Action): 

27 """Parse dates from CLI arguments into datetime.date.""" 

28 

29 def __call__(self, arg_parser, args, values, option_string=None): # type: ignore[no-untyped-def] 

30 due_date = date.fromisoformat(f"{values[0]:04}-{values[1]:02}-{values[2]:02}") 

31 setattr(args, self.dest, due_date) 

32 

33 

34class ArgumentParserFactory: # pylint: disable=too-many-public-methods 

35 """Argument Parser Factory to setup commonly used arguments.""" 

36 

37 def __init__(self, parser: ArgumentParser) -> None: 

38 self.__parser = parser 

39 

40 @staticmethod 

41 def default_parser(description: str) -> "ArgumentParserFactory": 

42 return ArgumentParserFactory( 

43 ArgumentParser( 

44 description=description, 

45 formatter_class=ArgumentDefaultsHelpFormatter, 

46 ) 

47 ) 

48 

49 @staticmethod 

50 def parent_parser() -> "ArgumentParserFactory": 

51 return ArgumentParserFactory(ArgumentParser(add_help=False)) 

52 

53 @staticmethod 

54 def create_default_date_arg() -> dict: 

55 return { 

56 "metavar": ("YEAR", "MONTH", "DAY"), 

57 "type": int, 

58 "nargs": 3, 

59 "action": DateAction, 

60 "default": None, 

61 } 

62 

63 @staticmethod 

64 def default_or_required_if_none(default: Any) -> dict: 

65 return {"required": True} if default is None else {"default": default} 

66 

67 @property 

68 def parser(self) -> ArgumentParser: 

69 return self.__parser 

70 

71 def copy(self) -> "ArgumentParserFactory": 

72 return copy.deepcopy(self) 

73 

74 def add_gitlab_token(self) -> None: 

75 self.__parser.add_argument( 

76 "-t", 

77 "--gitlab-token", 

78 type=str, 

79 required=True, 

80 help="Private gitlab token", 

81 ) 

82 

83 def add_student_repo_info_file(self) -> None: 

84 self.__parser.add_argument( 

85 "student_repo_info_file", 

86 type=FileType("r"), 

87 help="File which contains the student repositories info (name, id)", 

88 ) 

89 

90 def add_homework_number(self) -> None: 

91 self.__parser.add_argument( 

92 "-n", 

93 "--homework-number", 

94 type=int, 

95 required=True, 

96 help="Homework number as integer", 

97 ) 

98 

99 def add_workspace(self) -> None: 

100 self.__parser.add_argument( 

101 "-w", 

102 "--workspace", 

103 type=dir_path, 

104 default=REPO_DIR / "workspace", 

105 help="Path to the workspace where all repositories will be cloned/pulled", 

106 ) 

107 

108 def add_issue_md_slide(self) -> None: 

109 self.__parser.add_argument( 

110 "-i", 

111 "--issue-md-slides", 

112 type=FileType("r"), 

113 required=True, 

114 help="Path to the markdown slides used for creating the issues", 

115 ) 

116 

117 def add_due_date(self) -> None: 

118 self.__parser.add_argument( 

119 "-d", 

120 "--due-date", 

121 **ArgumentParserFactory.create_default_date_arg(), 

122 help="Due date for the homework's task(s)", 

123 ) 

124 

125 def add_date_sine_last_homework(self) -> None: 

126 self.__parser.add_argument( 

127 "-d", 

128 "--date-last-homework", 

129 **ArgumentParserFactory.create_default_date_arg(), 

130 help="Date of the last homework used to generate a diff. If no date is provided, no diff will be generated", 

131 ) 

132 

133 def add_message(self, help_text: str) -> None: 

134 self.__parser.add_argument( 

135 "-m", 

136 "--message", 

137 type=str, 

138 required=True, 

139 help=help_text, 

140 ) 

141 

142 def add_issue_number(self) -> None: 

143 self.__parser.add_argument( 

144 "-i", 

145 "--issue-number", 

146 type=int, 

147 required=True, 

148 help="Issue number the comment should be added", 

149 ) 

150 

151 def add_state_event(self) -> None: 

152 self.__parser.add_argument( 

153 "-s", 

154 "--state-event", 

155 type=str, 

156 choices=["close", "reopen"], 

157 default=None, 

158 help="Changes the state of the issue to", 

159 ) 

160 

161 def add_source_folder(self, default: Path | None) -> None: 

162 self.__parser.add_argument( 

163 "-s", 

164 "--source-path", 

165 type=dir_path, 

166 **ArgumentParserFactory.default_or_required_if_none(default), 

167 help="Path to the source files", 

168 ) 

169 

170 def add_number_of_repos(self) -> None: 

171 self.__parser.add_argument( 

172 "-n", 

173 "--number-of-repos", 

174 type=int, 

175 default=1, 

176 help="Number of repos to create", 

177 ) 

178 

179 def add_repo_info_dir(self) -> None: 

180 self.__parser.add_argument( 

181 "-r", 

182 "--repo-info-dir", 

183 type=dir_path, 

184 default=REPO_DIR / "config", 

185 help="Folder into which the config file containing the repositories info (name, id) will be saved", 

186 ) 

187 

188 def add_group_id(self) -> None: 

189 self.__parser.add_argument( 

190 "-g", 

191 "--group-id", 

192 type=int, 

193 required=True, 

194 help="ID of an existing GitLab group", 

195 ) 

196 

197 def add_repo_base_name(self) -> None: 

198 self.__parser.add_argument( 

199 "repo_base_name", 

200 type=str, 

201 help="Base name of the to-be-created repo(s)", 

202 ) 

203 

204 def add_output_path(self) -> None: 

205 self.__parser.add_argument( 

206 "-o", 

207 "--output-dir", 

208 default=REPO_DIR / "export", 

209 type=Path, 

210 help="Path to export location", 

211 ) 

212 

213 def add_keep_solutions(self) -> None: 

214 self.__parser.add_argument( 

215 "-k", 

216 "--keep-solutions", 

217 action="store_true", 

218 help="Keep solution blocks from exported files", 

219 ) 

220 

221 def add_publish_solutions(self) -> None: 

222 self.__parser.add_argument( 

223 "--publish-solutions", 

224 action="store_true", 

225 help="Publish code that contains solution code", 

226 ) 

227 

228 def add_evaluation_date(self) -> None: 

229 self.__parser.add_argument( 

230 "-e", 

231 "--evaluation-date", 

232 **ArgumentParserFactory.create_default_date_arg(), 

233 help="Date of the evaluation deadline", 

234 ) 

235 

236 def add_job_factory_path(self) -> None: 

237 self.__parser.add_argument( 

238 "-j", 

239 "--job-factory", 

240 type=file_path, 

241 default=REPO_DIR / "tools" / "sel_tools" / "code_evaluation" / "jobs" / "sel.py", 

242 help="Path to the python module containing the evaluation job factory", 

243 ) 

244 

245 def add_student_group_info_file(self) -> None: 

246 self.__parser.add_argument( 

247 "student_group_info_file", 

248 type=FileType("r"), 

249 help="File which contains the student groups info", 

250 )