Coverage for C:\Repos\ekr-pylint\pylint\config\argument.py: 36%

133 statements  

« prev     ^ index     » next       coverage.py v6.4, created at 2022-05-24 10:21 -0500

1# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html 

2# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE 

3# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt 

4 

5"""Definition of an Argument class and transformers for various argument types. 

6 

7An Argument instance represents a pylint option to be handled by an argparse.ArgumentParser 

8""" 

9 

10from __future__ import annotations 

11 

12import argparse 

13import os 

14import pathlib 

15import re 

16import sys 

17from collections.abc import Callable 

18from typing import Any, Pattern, Sequence, Tuple, Union 

19 

20from pylint import interfaces 

21from pylint import utils as pylint_utils 

22from pylint.config.callback_actions import _CallbackAction, _ExtendAction 

23from pylint.config.deprecation_actions import _NewNamesAction, _OldNamesAction 

24from pylint.constants import PY38_PLUS 

25 

26if sys.version_info >= (3, 8): 

27 from typing import Literal 

28else: 

29 from typing_extensions import Literal 

30 

31 

32_ArgumentTypes = Union[ 

33 str, 

34 int, 

35 float, 

36 bool, 

37 Pattern[str], 

38 Sequence[str], 

39 Sequence[Pattern[str]], 

40 Tuple[int, ...], 

41] 

42"""List of possible argument types.""" 

43 

44 

45def _confidence_transformer(value: str) -> Sequence[str]: 

46 """Transforms a comma separated string of confidence values.""" 

47 if not value: 

48 return interfaces.CONFIDENCE_LEVEL_NAMES 

49 values = pylint_utils._check_csv(value) 

50 for confidence in values: 

51 if confidence not in interfaces.CONFIDENCE_LEVEL_NAMES: 

52 raise argparse.ArgumentTypeError( 

53 f"{value} should be in {*interfaces.CONFIDENCE_LEVEL_NAMES,}" 

54 ) 

55 return values 

56 

57 

58def _csv_transformer(value: str) -> Sequence[str]: 

59 """Transforms a comma separated string.""" 

60 return pylint_utils._check_csv(value) 

61 

62 

63YES_VALUES = {"y", "yes", "true"} 

64NO_VALUES = {"n", "no", "false"} 

65 

66 

67def _yn_transformer(value: str) -> bool: 

68 """Transforms a yes/no or stringified bool into a bool.""" 

69 value = value.lower() 

70 if value in YES_VALUES: 

71 return True 

72 if value in NO_VALUES: 

73 return False 

74 raise argparse.ArgumentTypeError( 

75 None, f"Invalid yn value '{value}', should be in {*YES_VALUES, *NO_VALUES}" 

76 ) 

77 

78 

79def _non_empty_string_transformer(value: str) -> str: 

80 """Check that a string is not empty and remove quotes.""" 

81 if not value: 

82 raise argparse.ArgumentTypeError("Option cannot be an empty string.") 

83 return pylint_utils._unquote(value) 

84 

85 

86def _path_transformer(value: str) -> str: 

87 """Expand user and variables in a path.""" 

88 return os.path.expandvars(os.path.expanduser(value)) 

89 

90 

91def _py_version_transformer(value: str) -> tuple[int, ...]: 

92 """Transforms a version string into a version tuple.""" 

93 try: 

94 version = tuple(int(val) for val in value.replace(",", ".").split(".")) 

95 except ValueError: 

96 raise argparse.ArgumentTypeError( 

97 f"{value} has an invalid format, should be a version string. E.g., '3.8'" 

98 ) from None 

99 return version 

100 

101 

102def _regexp_csv_transfomer(value: str) -> Sequence[Pattern[str]]: 

103 """Transforms a comma separated list of regular expressions.""" 

104 patterns: list[Pattern[str]] = [] 

105 for pattern in _csv_transformer(value): 

106 patterns.append(re.compile(pattern)) 

107 return patterns 

108 

109 

110def _regexp_paths_csv_transfomer(value: str) -> Sequence[Pattern[str]]: 

111 """Transforms a comma separated list of regular expressions paths.""" 

112 patterns: list[Pattern[str]] = [] 

113 for pattern in _csv_transformer(value): 

114 patterns.append( 

115 re.compile( 

116 str(pathlib.PureWindowsPath(pattern)).replace("\\", "\\\\") 

117 + "|" 

118 + pathlib.PureWindowsPath(pattern).as_posix() 

119 ) 

120 ) 

121 return patterns 

122 

123 

124_TYPE_TRANSFORMERS: dict[str, Callable[[str], _ArgumentTypes]] = { 

125 "choice": str, 

126 "csv": _csv_transformer, 

127 "float": float, 

128 "int": int, 

129 "confidence": _confidence_transformer, 

130 "non_empty_string": _non_empty_string_transformer, 

131 "path": _path_transformer, 

132 "py_version": _py_version_transformer, 

133 "regexp": re.compile, 

134 "regexp_csv": _regexp_csv_transfomer, 

135 "regexp_paths_csv": _regexp_paths_csv_transfomer, 

136 "string": pylint_utils._unquote, 

137 "yn": _yn_transformer, 

138} 

139"""Type transformers for all argument types. 

140 

141A transformer should accept a string and return one of the supported 

142Argument types. It will only be called when parsing 1) command-line, 

1432) configuration files and 3) a string default value. 

144Non-string default values are assumed to be of the correct type. 

145""" 

146 

147 

148class _Argument: 

149 """Class representing an argument to be parsed by an argparse.ArgumentsParser. 

150 

151 This is based on the parameters passed to argparse.ArgumentsParser.add_message. 

152 See: 

153 https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument 

154 """ 

155 

156 def __init__( 

157 self, 

158 *, 

159 flags: list[str], 

160 arg_help: str, 

161 hide_help: bool, 

162 section: str | None, 

163 ) -> None: 

164 self.flags = flags 

165 """The name of the argument.""" 

166 

167 self.hide_help = hide_help 

168 """Whether to hide this argument in the help message.""" 

169 

170 # argparse uses % formatting on help strings, so a % needs to be escaped 

171 self.help = arg_help.replace("%", "%%") 

172 """The description of the argument.""" 

173 

174 if hide_help: 

175 self.help = argparse.SUPPRESS 

176 

177 self.section = section 

178 """The section to add this argument to.""" 

179 

180 

181class _BaseStoreArgument(_Argument): 

182 """Base class for store arguments to be parsed by an argparse.ArgumentsParser. 

183 

184 This is based on the parameters passed to argparse.ArgumentsParser.add_message. 

185 See: 

186 https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument 

187 """ 

188 

189 def __init__( 

190 self, 

191 *, 

192 flags: list[str], 

193 action: str, 

194 default: _ArgumentTypes, 

195 arg_help: str, 

196 hide_help: bool, 

197 section: str | None, 

198 ) -> None: 

199 super().__init__( 

200 flags=flags, arg_help=arg_help, hide_help=hide_help, section=section 

201 ) 

202 

203 self.action = action 

204 """The action to perform with the argument.""" 

205 

206 self.default = default 

207 """The default value of the argument.""" 

208 

209 

210class _StoreArgument(_BaseStoreArgument): 

211 """Class representing a store argument to be parsed by an argparse.ArgumentsParser. 

212 

213 This is based on the parameters passed to argparse.ArgumentsParser.add_message. 

214 See: 

215 https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument 

216 """ 

217 

218 def __init__( 

219 self, 

220 *, 

221 flags: list[str], 

222 action: str, 

223 default: _ArgumentTypes, 

224 arg_type: str, 

225 choices: list[str] | None, 

226 arg_help: str, 

227 metavar: str, 

228 hide_help: bool, 

229 section: str | None, 

230 ) -> None: 

231 super().__init__( 

232 flags=flags, 

233 action=action, 

234 default=default, 

235 arg_help=arg_help, 

236 hide_help=hide_help, 

237 section=section, 

238 ) 

239 

240 self.type = _TYPE_TRANSFORMERS[arg_type] 

241 """A transformer function that returns a transformed type of the argument.""" 

242 

243 self.choices = choices 

244 """A list of possible choices for the argument. 

245 

246 None if there are no restrictions. 

247 """ 

248 

249 self.metavar = metavar 

250 """The metavar of the argument. 

251 

252 See: 

253 https://docs.python.org/3/library/argparse.html#metavar 

254 """ 

255 

256 

257class _StoreTrueArgument(_BaseStoreArgument): 

258 """Class representing a 'store_true' argument to be parsed by an argparse.ArgumentsParser. 

259 

260 This is based on the parameters passed to argparse.ArgumentsParser.add_message. 

261 See: 

262 https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument 

263 """ 

264 

265 # pylint: disable-next=useless-super-delegation # We narrow down the type of action 

266 def __init__( 

267 self, 

268 *, 

269 flags: list[str], 

270 action: Literal["store_true"], 

271 default: _ArgumentTypes, 

272 arg_help: str, 

273 hide_help: bool, 

274 section: str | None, 

275 ) -> None: 

276 super().__init__( 

277 flags=flags, 

278 action=action, 

279 default=default, 

280 arg_help=arg_help, 

281 hide_help=hide_help, 

282 section=section, 

283 ) 

284 

285 

286class _DeprecationArgument(_Argument): 

287 """Store arguments while also handling deprecation warnings for old and new names. 

288 

289 This is based on the parameters passed to argparse.ArgumentsParser.add_message. 

290 See: 

291 https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument 

292 """ 

293 

294 def __init__( 

295 self, 

296 *, 

297 flags: list[str], 

298 action: type[argparse.Action], 

299 default: _ArgumentTypes, 

300 arg_type: str, 

301 choices: list[str] | None, 

302 arg_help: str, 

303 metavar: str, 

304 hide_help: bool, 

305 section: str | None, 

306 ) -> None: 

307 super().__init__( 

308 flags=flags, arg_help=arg_help, hide_help=hide_help, section=section 

309 ) 

310 

311 self.action = action 

312 """The action to perform with the argument.""" 

313 

314 self.default = default 

315 """The default value of the argument.""" 

316 

317 self.type = _TYPE_TRANSFORMERS[arg_type] 

318 """A transformer function that returns a transformed type of the argument.""" 

319 

320 self.choices = choices 

321 """A list of possible choices for the argument. 

322 

323 None if there are no restrictions. 

324 """ 

325 

326 self.metavar = metavar 

327 """The metavar of the argument. 

328 

329 See: 

330 https://docs.python.org/3/library/argparse.html#metavar 

331 """ 

332 

333 

334class _ExtendArgument(_DeprecationArgument): 

335 """Class for extend arguments to be parsed by an argparse.ArgumentsParser. 

336 

337 This is based on the parameters passed to argparse.ArgumentsParser.add_message. 

338 See: 

339 https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument 

340 """ 

341 

342 def __init__( 

343 self, 

344 *, 

345 flags: list[str], 

346 action: Literal["extend"], 

347 default: _ArgumentTypes, 

348 arg_type: str, 

349 metavar: str, 

350 arg_help: str, 

351 hide_help: bool, 

352 section: str | None, 

353 choices: list[str] | None, 

354 dest: str | None, 

355 ) -> None: 

356 # The extend action is included in the stdlib from 3.8+ 

357 if PY38_PLUS: 

358 action_class = argparse._ExtendAction # type: ignore[attr-defined] 

359 else: 

360 action_class = _ExtendAction 

361 

362 self.dest = dest 

363 """The destination of the argument.""" 

364 

365 super().__init__( 

366 flags=flags, 

367 action=action_class, 

368 default=default, 

369 arg_type=arg_type, 

370 choices=choices, 

371 arg_help=arg_help, 

372 metavar=metavar, 

373 hide_help=hide_help, 

374 section=section, 

375 ) 

376 

377 

378class _StoreOldNamesArgument(_DeprecationArgument): 

379 """Store arguments while also handling old names. 

380 

381 This is based on the parameters passed to argparse.ArgumentsParser.add_message. 

382 See: 

383 https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument 

384 """ 

385 

386 def __init__( 

387 self, 

388 *, 

389 flags: list[str], 

390 default: _ArgumentTypes, 

391 arg_type: str, 

392 choices: list[str] | None, 

393 arg_help: str, 

394 metavar: str, 

395 hide_help: bool, 

396 kwargs: dict[str, Any], 

397 section: str | None, 

398 ) -> None: 

399 super().__init__( 

400 flags=flags, 

401 action=_OldNamesAction, 

402 default=default, 

403 arg_type=arg_type, 

404 choices=choices, 

405 arg_help=arg_help, 

406 metavar=metavar, 

407 hide_help=hide_help, 

408 section=section, 

409 ) 

410 

411 self.kwargs = kwargs 

412 """Any additional arguments passed to the action.""" 

413 

414 

415class _StoreNewNamesArgument(_DeprecationArgument): 

416 """Store arguments while also emitting deprecation warnings. 

417 

418 This is based on the parameters passed to argparse.ArgumentsParser.add_message. 

419 See: 

420 https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument 

421 """ 

422 

423 def __init__( 

424 self, 

425 *, 

426 flags: list[str], 

427 default: _ArgumentTypes, 

428 arg_type: str, 

429 choices: list[str] | None, 

430 arg_help: str, 

431 metavar: str, 

432 hide_help: bool, 

433 kwargs: dict[str, Any], 

434 section: str | None, 

435 ) -> None: 

436 super().__init__( 

437 flags=flags, 

438 action=_NewNamesAction, 

439 default=default, 

440 arg_type=arg_type, 

441 choices=choices, 

442 arg_help=arg_help, 

443 metavar=metavar, 

444 hide_help=hide_help, 

445 section=section, 

446 ) 

447 

448 self.kwargs = kwargs 

449 """Any additional arguments passed to the action.""" 

450 

451 

452class _CallableArgument(_Argument): 

453 """Class representing an callable argument to be parsed by an argparse.ArgumentsParser. 

454 

455 This is based on the parameters passed to argparse.ArgumentsParser.add_message. 

456 See: 

457 https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument 

458 """ 

459 

460 def __init__( 

461 self, 

462 *, 

463 flags: list[str], 

464 action: type[_CallbackAction], 

465 arg_help: str, 

466 kwargs: dict[str, Any], 

467 hide_help: bool, 

468 section: str | None, 

469 metavar: str, 

470 ) -> None: 

471 super().__init__( 

472 flags=flags, arg_help=arg_help, hide_help=hide_help, section=section 

473 ) 

474 

475 self.action = action 

476 """The action to perform with the argument.""" 

477 

478 self.kwargs = kwargs 

479 """Any additional arguments passed to the action.""" 

480 

481 self.metavar = metavar 

482 """The metavar of the argument. 

483 

484 See: 

485 https://docs.python.org/3/library/argparse.html#metavar 

486 """