Coverage for C:\Repos\ekr-pylint\pylint\config\arguments_manager.py: 30%

172 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"""Arguments manager class used to handle command-line arguments and options.""" 

6 

7from __future__ import annotations 

8 

9import argparse 

10import configparser 

11import copy 

12import optparse # pylint: disable=deprecated-module 

13import os 

14import re 

15import sys 

16import textwrap 

17import warnings 

18from collections import OrderedDict 

19from collections.abc import Sequence 

20from pathlib import Path 

21from typing import TYPE_CHECKING, Any, TextIO, Union 

22 

23import tomlkit 

24 

25from pylint import utils 

26from pylint.config.argument import ( 

27 _Argument, 

28 _CallableArgument, 

29 _ExtendArgument, 

30 _StoreArgument, 

31 _StoreNewNamesArgument, 

32 _StoreOldNamesArgument, 

33 _StoreTrueArgument, 

34) 

35from pylint.config.exceptions import ( 

36 UnrecognizedArgumentAction, 

37 _UnrecognizedOptionError, 

38) 

39from pylint.config.help_formatter import _HelpFormatter 

40from pylint.config.option import Option 

41from pylint.config.option_parser import OptionParser 

42from pylint.config.options_provider_mixin import OptionsProviderMixIn 

43from pylint.config.utils import _convert_option_to_argument, _parse_rich_type_value 

44from pylint.constants import MAIN_CHECKER_NAME 

45from pylint.typing import OptionDict 

46 

47if sys.version_info >= (3, 11): 

48 import tomllib 

49else: 

50 import tomli as tomllib 

51 

52 

53if TYPE_CHECKING: 

54 from pylint.config.arguments_provider import _ArgumentsProvider 

55 

56ConfigProvider = Union["_ArgumentsProvider", OptionsProviderMixIn] 

57 

58 

59# pylint: disable-next=too-many-instance-attributes 

60class _ArgumentsManager: 

61 """Arguments manager class used to handle command-line arguments and options.""" 

62 

63 def __init__( 

64 self, prog: str, usage: str | None = None, description: str | None = None 

65 ) -> None: 

66 self._config = argparse.Namespace() 

67 """Namespace for all options.""" 

68 

69 self._arg_parser = argparse.ArgumentParser( 

70 prog=prog, 

71 usage=usage or "%(prog)s [options]", 

72 description=description, 

73 formatter_class=_HelpFormatter, 

74 ) 

75 """The command line argument parser.""" 

76 

77 self._argument_groups_dict: dict[str, argparse._ArgumentGroup] = {} 

78 """Dictionary of all the argument groups.""" 

79 

80 self._option_dicts: dict[str, OptionDict] = {} 

81 """All option dictionaries that have been registered.""" 

82 

83 # TODO: 3.0: Remove deprecated attributes introduced to keep API 

84 # parity with optparse. Until '_maxlevel' 

85 with warnings.catch_warnings(): 

86 warnings.filterwarnings("ignore", category=DeprecationWarning) 

87 self.reset_parsers(usage or "") 

88 # list of registered options providers 

89 self._options_providers: list[ConfigProvider] = [] 

90 # dictionary associating option name to checker 

91 self._all_options: OrderedDict[str, ConfigProvider] = OrderedDict() 

92 self._short_options: dict[str, str] = {} 

93 self._nocallback_options: dict[ConfigProvider, str] = {} 

94 self._mygroups: dict[str, optparse.OptionGroup] = {} 

95 # verbosity 

96 self._maxlevel: int = 0 

97 

98 @property 

99 def config(self) -> argparse.Namespace: 

100 """Namespace for all options.""" 

101 return self._config 

102 

103 @config.setter 

104 def config(self, value: argparse.Namespace) -> None: 

105 self._config = value 

106 

107 @property 

108 def options_providers(self) -> list[ConfigProvider]: 

109 # TODO: 3.0: Remove deprecated attribute. 

110 warnings.warn( 

111 "options_providers has been deprecated. It will be removed in pylint 3.0.", 

112 DeprecationWarning, 

113 ) 

114 return self._options_providers 

115 

116 @options_providers.setter 

117 def options_providers(self, value: list[ConfigProvider]) -> None: 

118 warnings.warn( 

119 "Setting options_providers has been deprecated. It will be removed in pylint 3.0.", 

120 DeprecationWarning, 

121 ) 

122 self._options_providers = value 

123 

124 def _register_options_provider(self, provider: _ArgumentsProvider) -> None: 

125 """Register an options provider and load its defaults.""" 

126 for opt, optdict in provider.options: 

127 self._option_dicts[opt] = optdict 

128 argument = _convert_option_to_argument(opt, optdict) 

129 section = argument.section or provider.name.capitalize() 

130 

131 section_desc = provider.option_groups_descs.get(section, None) 

132 

133 # We exclude main since its docstring comes from PyLinter 

134 if provider.name != MAIN_CHECKER_NAME and provider.__doc__: 

135 section_desc = provider.__doc__.split("\n\n")[0] 

136 

137 self._add_arguments_to_parser(section, section_desc, argument) 

138 

139 self._load_default_argument_values() 

140 

141 def _add_arguments_to_parser( 

142 self, section: str, section_desc: str | None, argument: _Argument 

143 ) -> None: 

144 """Add an argument to the correct argument section/group.""" 

145 try: 

146 section_group = self._argument_groups_dict[section] 

147 except KeyError: 

148 if section_desc: 

149 section_group = self._arg_parser.add_argument_group( 

150 section, section_desc 

151 ) 

152 else: 

153 section_group = self._arg_parser.add_argument_group(title=section) 

154 self._argument_groups_dict[section] = section_group 

155 self._add_parser_option(section_group, argument) 

156 

157 @staticmethod 

158 def _add_parser_option( 

159 section_group: argparse._ArgumentGroup, argument: _Argument 

160 ) -> None: 

161 """Add an argument.""" 

162 if isinstance(argument, _StoreArgument): 

163 section_group.add_argument( 

164 *argument.flags, 

165 action=argument.action, 

166 default=argument.default, 

167 type=argument.type, # type: ignore[arg-type] # incorrect typing in typeshed 

168 help=argument.help, 

169 metavar=argument.metavar, 

170 choices=argument.choices, 

171 ) 

172 elif isinstance(argument, _StoreOldNamesArgument): 

173 section_group.add_argument( 

174 *argument.flags, 

175 **argument.kwargs, 

176 action=argument.action, 

177 default=argument.default, 

178 type=argument.type, # type: ignore[arg-type] # incorrect typing in typeshed 

179 help=argument.help, 

180 metavar=argument.metavar, 

181 choices=argument.choices, 

182 ) 

183 # We add the old name as hidden option to make it's default value gets loaded when 

184 # argparse initializes all options from the checker 

185 assert argument.kwargs["old_names"] 

186 for old_name in argument.kwargs["old_names"]: 

187 section_group.add_argument( 

188 f"--{old_name}", 

189 action="store", 

190 default=argument.default, 

191 type=argument.type, # type: ignore[arg-type] # incorrect typing in typeshed 

192 help=argparse.SUPPRESS, 

193 metavar=argument.metavar, 

194 choices=argument.choices, 

195 ) 

196 elif isinstance(argument, _StoreNewNamesArgument): 

197 section_group.add_argument( 

198 *argument.flags, 

199 **argument.kwargs, 

200 action=argument.action, 

201 default=argument.default, 

202 type=argument.type, # type: ignore[arg-type] # incorrect typing in typeshed 

203 help=argument.help, 

204 metavar=argument.metavar, 

205 choices=argument.choices, 

206 ) 

207 elif isinstance(argument, _StoreTrueArgument): 

208 section_group.add_argument( 

209 *argument.flags, 

210 action=argument.action, 

211 default=argument.default, 

212 help=argument.help, 

213 ) 

214 elif isinstance(argument, _CallableArgument): 

215 section_group.add_argument( 

216 *argument.flags, 

217 **argument.kwargs, 

218 action=argument.action, 

219 help=argument.help, 

220 metavar=argument.metavar, 

221 ) 

222 elif isinstance(argument, _ExtendArgument): 

223 section_group.add_argument( 

224 *argument.flags, 

225 action=argument.action, 

226 default=argument.default, 

227 type=argument.type, # type: ignore[arg-type] # incorrect typing in typeshed 

228 help=argument.help, 

229 metavar=argument.metavar, 

230 choices=argument.choices, 

231 dest=argument.dest, 

232 ) 

233 else: 

234 raise UnrecognizedArgumentAction 

235 

236 def _load_default_argument_values(self) -> None: 

237 """Loads the default values of all registered options.""" 

238 self.config = self._arg_parser.parse_args([], self.config) 

239 

240 def _parse_configuration_file(self, arguments: list[str]) -> None: 

241 """Parse the arguments found in a configuration file into the namespace.""" 

242 self.config, parsed_args = self._arg_parser.parse_known_args( 

243 arguments, self.config 

244 ) 

245 unrecognized_options: list[str] = [] 

246 for opt in parsed_args: 

247 if opt.startswith("--"): 

248 unrecognized_options.append(opt[2:]) 

249 if unrecognized_options: 

250 raise _UnrecognizedOptionError(options=unrecognized_options) 

251 

252 def _parse_command_line_configuration( 

253 self, arguments: Sequence[str] | None = None 

254 ) -> list[str]: 

255 """Parse the arguments found on the command line into the namespace.""" 

256 arguments = sys.argv[1:] if arguments is None else arguments 

257 

258 self.config, parsed_args = self._arg_parser.parse_known_args( 

259 arguments, self.config 

260 ) 

261 

262 return parsed_args 

263 

264 def reset_parsers(self, usage: str = "") -> None: # pragma: no cover 

265 """DEPRECATED.""" 

266 warnings.warn( 

267 "reset_parsers has been deprecated. Parsers should be instantiated " 

268 "once during initialization and do not need to be reset.", 

269 DeprecationWarning, 

270 ) 

271 # configuration file parser 

272 self.cfgfile_parser = configparser.ConfigParser( 

273 inline_comment_prefixes=("#", ";") 

274 ) 

275 # command line parser 

276 self.cmdline_parser = OptionParser(Option, usage=usage) 

277 self.cmdline_parser.options_manager = self # type: ignore[attr-defined] 

278 self._optik_option_attrs = set(self.cmdline_parser.option_class.ATTRS) 

279 

280 def register_options_provider( 

281 self, provider: ConfigProvider, own_group: bool = True 

282 ) -> None: # pragma: no cover 

283 """DEPRECATED: Register an options provider.""" 

284 warnings.warn( 

285 "register_options_provider has been deprecated. Options providers and " 

286 "arguments providers should be registered by initializing ArgumentsProvider. " 

287 "This automatically registers the provider on the ArgumentsManager.", 

288 DeprecationWarning, 

289 ) 

290 self.options_providers.append(provider) 

291 non_group_spec_options = [ 

292 option for option in provider.options if "group" not in option[1] 

293 ] 

294 groups = getattr(provider, "option_groups", ()) 

295 if own_group and non_group_spec_options: 

296 with warnings.catch_warnings(): 

297 warnings.filterwarnings("ignore", category=DeprecationWarning) 

298 self.add_option_group( 

299 provider.name.upper(), 

300 provider.__doc__, 

301 non_group_spec_options, 

302 provider, 

303 ) 

304 else: 

305 for opt, optdict in non_group_spec_options: 

306 with warnings.catch_warnings(): 

307 warnings.filterwarnings("ignore", category=DeprecationWarning) 

308 self.add_optik_option(provider, self.cmdline_parser, opt, optdict) 

309 for gname, gdoc in groups: 

310 gname = gname.upper() 

311 goptions = [ 

312 option 

313 for option in provider.options 

314 if option[1].get("group", "").upper() == gname # type: ignore[union-attr] 

315 ] 

316 with warnings.catch_warnings(): 

317 warnings.filterwarnings("ignore", category=DeprecationWarning) 

318 self.add_option_group(gname, gdoc, goptions, provider) 

319 

320 def add_option_group( 

321 self, 

322 group_name: str, 

323 _: str | None, 

324 options: list[tuple[str, OptionDict]], 

325 provider: ConfigProvider, 

326 ) -> None: # pragma: no cover 

327 """DEPRECATED.""" 

328 warnings.warn( 

329 "add_option_group has been deprecated. Option groups should be " 

330 "registered by initializing ArgumentsProvider. " 

331 "This automatically registers the group on the ArgumentsManager.", 

332 DeprecationWarning, 

333 ) 

334 # add option group to the command line parser 

335 if group_name in self._mygroups: 

336 group = self._mygroups[group_name] 

337 else: 

338 group = optparse.OptionGroup( 

339 self.cmdline_parser, title=group_name.capitalize() 

340 ) 

341 self.cmdline_parser.add_option_group(group) 

342 self._mygroups[group_name] = group 

343 # add section to the config file 

344 if ( 

345 group_name != "DEFAULT" 

346 and group_name not in self.cfgfile_parser._sections # type: ignore[attr-defined] 

347 ): 

348 self.cfgfile_parser.add_section(group_name) 

349 # add provider's specific options 

350 for opt, optdict in options: 

351 if not isinstance(optdict.get("action", "store"), str): 

352 optdict["action"] = "callback" 

353 with warnings.catch_warnings(): 

354 warnings.filterwarnings("ignore", category=DeprecationWarning) 

355 self.add_optik_option(provider, group, opt, optdict) 

356 

357 def add_optik_option( 

358 self, 

359 provider: ConfigProvider, 

360 optikcontainer: optparse.OptionParser | optparse.OptionGroup, 

361 opt: str, 

362 optdict: OptionDict, 

363 ) -> None: # pragma: no cover 

364 """DEPRECATED.""" 

365 warnings.warn( 

366 "add_optik_option has been deprecated. Options should be automatically " 

367 "added by initializing an ArgumentsProvider.", 

368 DeprecationWarning, 

369 ) 

370 with warnings.catch_warnings(): 

371 warnings.filterwarnings("ignore", category=DeprecationWarning) 

372 args, optdict = self.optik_option(provider, opt, optdict) 

373 option = optikcontainer.add_option(*args, **optdict) 

374 self._all_options[opt] = provider 

375 self._maxlevel = max(self._maxlevel, option.level or 0) 

376 

377 def optik_option( 

378 self, provider: ConfigProvider, opt: str, optdict: OptionDict 

379 ) -> tuple[list[str], OptionDict]: # pragma: no cover 

380 """DEPRECATED: Get our personal option definition and return a suitable form for 

381 use with optik/optparse. 

382 """ 

383 warnings.warn( 

384 "optik_option has been deprecated. Parsing of option dictionaries should be done " 

385 "automatically by initializing an ArgumentsProvider.", 

386 DeprecationWarning, 

387 ) 

388 optdict = copy.copy(optdict) 

389 if "action" in optdict: 

390 self._nocallback_options[provider] = opt 

391 else: 

392 optdict["action"] = "callback" 

393 optdict["callback"] = self.cb_set_provider_option 

394 # default is handled here and *must not* be given to optik if you 

395 # want the whole machinery to work 

396 if "default" in optdict: 

397 if ( 

398 "help" in optdict 

399 and optdict.get("default") is not None 

400 and optdict["action"] not in ("store_true", "store_false") 

401 ): 

402 optdict["help"] += " [current: %default]" # type: ignore[operator] 

403 del optdict["default"] 

404 args = ["--" + str(opt)] 

405 if "short" in optdict: 

406 self._short_options[optdict["short"]] = opt # type: ignore[index] 

407 args.append("-" + optdict["short"]) # type: ignore[operator] 

408 del optdict["short"] 

409 # cleanup option definition dict before giving it to optik 

410 for key in list(optdict.keys()): 

411 if key not in self._optik_option_attrs: 

412 optdict.pop(key) 

413 return args, optdict 

414 

415 def generate_config( 

416 self, stream: TextIO | None = None, skipsections: tuple[str, ...] = () 

417 ) -> None: # pragma: no cover 

418 """DEPRECATED: Write a configuration file according to the current configuration 

419 into the given stream or stdout. 

420 """ 

421 warnings.warn( 

422 "generate_config has been deprecated. It will be removed in pylint 3.0.", 

423 DeprecationWarning, 

424 ) 

425 options_by_section = {} 

426 sections = [] 

427 for group in self._arg_parser._action_groups: 

428 group_name = group.title 

429 assert group_name 

430 if group_name in skipsections: 

431 continue 

432 

433 options = [] 

434 for opt in group._group_actions: 

435 if "--help" in opt.option_strings: 

436 continue 

437 

438 optname = opt.option_strings[0][2:] 

439 

440 try: 

441 optdict = self._option_dicts[optname] 

442 except KeyError: 

443 continue 

444 

445 options.append( 

446 ( 

447 optname, 

448 optdict, 

449 getattr(self.config, optname.replace("-", "_")), 

450 ) 

451 ) 

452 

453 options = [ 

454 (n, d, v) for (n, d, v) in options if not d.get("deprecated") 

455 ] 

456 

457 if options: 

458 sections.append(group_name) 

459 options_by_section[group_name] = options 

460 stream = stream or sys.stdout 

461 printed = False 

462 for section in sections: 

463 if printed: 

464 print("\n", file=stream) 

465 with warnings.catch_warnings(): 

466 warnings.filterwarnings("ignore", category=DeprecationWarning) 

467 utils.format_section( 

468 stream, section.upper(), sorted(options_by_section[section]) 

469 ) 

470 printed = True 

471 

472 def load_provider_defaults(self) -> None: # pragma: no cover 

473 """DEPRECATED: Initialize configuration using default values.""" 

474 warnings.warn( 

475 "load_provider_defaults has been deprecated. Parsing of option defaults should be done " 

476 "automatically by initializing an ArgumentsProvider.", 

477 DeprecationWarning, 

478 ) 

479 for provider in self.options_providers: 

480 with warnings.catch_warnings(): 

481 warnings.filterwarnings("ignore", category=DeprecationWarning) 

482 provider.load_defaults() 

483 

484 def read_config_file( 

485 self, config_file: Path | None = None, verbose: bool = False 

486 ) -> None: # pragma: no cover 

487 """DEPRECATED: Read the configuration file but do not load it (i.e. dispatching 

488 values to each option's provider). 

489 

490 :raises OSError: When the specified config file doesn't exist 

491 """ 

492 warnings.warn( 

493 "read_config_file has been deprecated. It will be removed in pylint 3.0.", 

494 DeprecationWarning, 

495 ) 

496 if not config_file: 

497 if verbose: 

498 print( 

499 "No config file found, using default configuration", file=sys.stderr 

500 ) 

501 return 

502 config_file = Path(os.path.expandvars(config_file)).expanduser() 

503 if not config_file.exists(): 

504 raise OSError(f"The config file {str(config_file)} doesn't exist!") 

505 parser = self.cfgfile_parser 

506 if config_file.suffix == ".toml": 

507 try: 

508 self._parse_toml(config_file, parser) 

509 except tomllib.TOMLDecodeError: 

510 pass 

511 else: 

512 # Use this encoding in order to strip the BOM marker, if any. 

513 with open(config_file, encoding="utf_8_sig") as fp: 

514 parser.read_file(fp) 

515 # normalize each section's title 

516 for sect, values in list(parser._sections.items()): # type: ignore[attr-defined] 

517 if sect.startswith("pylint."): 

518 sect = sect[len("pylint.") :] 

519 if not sect.isupper() and values: 

520 parser._sections[sect.upper()] = values # type: ignore[attr-defined] 

521 

522 if verbose: 

523 print(f"Using config file '{config_file}'", file=sys.stderr) 

524 

525 @staticmethod 

526 def _parse_toml( 

527 config_file: Path, parser: configparser.ConfigParser 

528 ) -> None: # pragma: no cover 

529 """DEPRECATED: Parse and handle errors of a toml configuration file. 

530 

531 TODO: 3.0: Remove deprecated method. 

532 """ 

533 with open(config_file, mode="rb") as fp: 

534 content = tomllib.load(fp) 

535 try: 

536 sections_values = content["tool"]["pylint"] 

537 except KeyError: 

538 return 

539 for section, values in sections_values.items(): 

540 section_name = section.upper() 

541 # TOML has rich types, convert values to 

542 # strings as ConfigParser expects. 

543 if not isinstance(values, dict): 

544 continue 

545 for option, value in values.items(): 

546 if isinstance(value, bool): 

547 values[option] = "yes" if value else "no" 

548 elif isinstance(value, list): 

549 values[option] = ",".join(value) 

550 else: 

551 values[option] = str(value) 

552 for option, value in values.items(): 

553 try: 

554 parser.set(section_name, option, value=value) 

555 except configparser.NoSectionError: 

556 parser.add_section(section_name) 

557 parser.set(section_name, option, value=value) 

558 

559 def load_config_file(self) -> None: # pragma: no cover 

560 """DEPRECATED: Dispatch values previously read from a configuration file to each 

561 option's provider. 

562 """ 

563 warnings.warn( 

564 "load_config_file has been deprecated. It will be removed in pylint 3.0.", 

565 DeprecationWarning, 

566 ) 

567 parser = self.cfgfile_parser 

568 for section in parser.sections(): 

569 for option, value in parser.items(section): 

570 try: 

571 self.global_set_option(option, value) 

572 except (KeyError, optparse.OptionError): 

573 continue 

574 

575 def load_configuration(self, **kwargs: Any) -> None: # pragma: no cover 

576 """DEPRECATED: Override configuration according to given parameters.""" 

577 warnings.warn( 

578 "load_configuration has been deprecated. It will be removed in pylint 3.0.", 

579 DeprecationWarning, 

580 ) 

581 with warnings.catch_warnings(): 

582 warnings.filterwarnings("ignore", category=DeprecationWarning) 

583 return self.load_configuration_from_config(kwargs) 

584 

585 def load_configuration_from_config( 

586 self, config: dict[str, Any] 

587 ) -> None: # pragma: no cover 

588 warnings.warn( 

589 "DEPRECATED: load_configuration_from_config has been deprecated. It will be removed in pylint 3.0.", 

590 DeprecationWarning, 

591 ) 

592 for opt, opt_value in config.items(): 

593 opt = opt.replace("_", "-") 

594 provider = self._all_options[opt] 

595 provider.set_option(opt, opt_value) 

596 

597 def load_command_line_configuration( 

598 self, args: list[str] | None = None 

599 ) -> list[str]: # pragma: no cover 

600 """DEPRECATED: Override configuration according to command line parameters. 

601 

602 return additional arguments 

603 """ 

604 warnings.warn( 

605 "load_command_line_configuration has been deprecated. It will be removed in pylint 3.0.", 

606 DeprecationWarning, 

607 ) 

608 args = sys.argv[1:] if args is None else list(args) 

609 (options, args) = self.cmdline_parser.parse_args(args=args) 

610 for provider in self._nocallback_options: 

611 config = provider.config 

612 for attr in config.__dict__.keys(): 

613 value = getattr(options, attr, None) 

614 if value is None: 

615 continue 

616 setattr(config, attr, value) 

617 return args 

618 

619 def help(self, level: int | None = None) -> str: 

620 """Return the usage string based on the available options.""" 

621 if level is not None: 

622 warnings.warn( 

623 "Supplying a 'level' argument to help() has been deprecated." 

624 "You can call help() without any arguments.", 

625 DeprecationWarning, 

626 ) 

627 return self._arg_parser.format_help() 

628 

629 def cb_set_provider_option(self, option, opt, value, parser): # pragma: no cover 

630 """DEPRECATED: Optik callback for option setting.""" 

631 # TODO: 3.0: Remove deprecated method. 

632 warnings.warn( 

633 "cb_set_provider_option has been deprecated. It will be removed in pylint 3.0.", 

634 DeprecationWarning, 

635 ) 

636 if opt.startswith("--"): 

637 # remove -- on long option 

638 opt = opt[2:] 

639 else: 

640 # short option, get its long equivalent 

641 opt = self._short_options[opt[1:]] 

642 # trick since we can't set action='store_true' on options 

643 if value is None: 

644 value = 1 

645 self.set_option(opt, value) 

646 

647 def global_set_option(self, opt: str, value: Any) -> None: # pragma: no cover 

648 """DEPRECATED: Set option on the correct option provider.""" 

649 # TODO: 3.0: Remove deprecated method. 

650 warnings.warn( 

651 "global_set_option has been deprecated. You can use _arguments_manager.set_option " 

652 "or linter.set_option to set options on the global configuration object.", 

653 DeprecationWarning, 

654 ) 

655 self.set_option(opt, value) 

656 

657 def _generate_config_file(self) -> None: 

658 """Write a configuration file according to the current configuration into stdout.""" 

659 toml_doc = tomlkit.document() 

660 pylint_tool_table = tomlkit.table(is_super_table=True) 

661 toml_doc.add(tomlkit.key(["tool", "pylint"]), pylint_tool_table) 

662 

663 for group in sorted( 

664 self._arg_parser._action_groups, 

665 key=lambda x: (x.title != "Main", x.title), 

666 ): 

667 # Skip the options section with the --help option 

668 if group.title in {"options", "optional arguments", "Commands"}: 

669 continue 

670 

671 # Skip sections without options such as "positional arguments" 

672 if not group._group_actions: 

673 continue 

674 

675 group_table = tomlkit.table() 

676 for action in sorted( 

677 group._group_actions, key=lambda x: x.option_strings[0][2:] 

678 ): 

679 optname = action.option_strings[0][2:] 

680 

681 # We skip old name options that don't have their own optdict 

682 try: 

683 optdict = self._option_dicts[optname] 

684 except KeyError: 

685 continue 

686 

687 if optdict.get("hide_from_config_file"): 

688 continue 

689 

690 # Add help comment 

691 help_msg = optdict.get("help", "") 

692 assert isinstance(help_msg, str) 

693 help_text = textwrap.wrap(help_msg, width=79) 

694 for line in help_text: 

695 group_table.add(tomlkit.comment(line)) 

696 

697 # Get current value of option 

698 value = getattr(self.config, optname.replace("-", "_")) 

699 

700 # Create a comment if the option has no value 

701 if not value: 

702 group_table.add(tomlkit.comment(f"{optname} =")) 

703 group_table.add(tomlkit.nl()) 

704 continue 

705 

706 # Skip deprecated options 

707 if "kwargs" in optdict: 

708 assert isinstance(optdict["kwargs"], dict) 

709 if "new_names" in optdict["kwargs"]: 

710 continue 

711 

712 # Tomlkit doesn't support regular expressions 

713 if isinstance(value, re.Pattern): 

714 value = value.pattern 

715 elif isinstance(value, (list, tuple)) and isinstance( 

716 value[0], re.Pattern 

717 ): 

718 value = [i.pattern for i in value] 

719 

720 # Handle tuples that should be strings 

721 if optdict.get("type") == "py_version": 

722 value = ".".join(str(i) for i in value) 

723 

724 # Add to table 

725 group_table.add(optname, value) 

726 group_table.add(tomlkit.nl()) 

727 

728 assert group.title 

729 pylint_tool_table.add(group.title.lower(), group_table) 

730 

731 toml_string = tomlkit.dumps(toml_doc) 

732 

733 # Make sure the string we produce is valid toml and can be parsed 

734 tomllib.loads(toml_string) 

735 

736 print(toml_string) 

737 

738 def set_option( 

739 self, 

740 optname: str, 

741 value: Any, 

742 action: str | None = "default_value", 

743 optdict: None | str | OptionDict = "default_value", 

744 ) -> None: 

745 """Set an option on the namespace object.""" 

746 # TODO: 3.0: Remove deprecated arguments. 

747 if action != "default_value": 

748 warnings.warn( 

749 "The 'action' argument has been deprecated. You can use set_option " 

750 "without the 'action' or 'optdict' arguments.", 

751 DeprecationWarning, 

752 ) 

753 if optdict != "default_value": 

754 warnings.warn( 

755 "The 'optdict' argument has been deprecated. You can use set_option " 

756 "without the 'action' or 'optdict' arguments.", 

757 DeprecationWarning, 

758 ) 

759 

760 self.config = self._arg_parser.parse_known_args( 

761 [f"--{optname.replace('_', '-')}", _parse_rich_type_value(value)], 

762 self.config, 

763 )[0]