Coverage for C:\Repos\ekr-pylint\pylint\config\config_file_parser.py: 24%
75 statements
« prev ^ index » next coverage.py v6.4, created at 2022-05-24 10:21 -0500
« 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
5"""Configuration file parser class."""
7from __future__ import annotations
9import configparser
10import os
11import sys
12import warnings
13from pathlib import Path
14from typing import TYPE_CHECKING
16from pylint.config.utils import _parse_rich_type_value
18if sys.version_info >= (3, 11):
19 import tomllib
20else:
21 import tomli as tomllib
23if TYPE_CHECKING:
24 from pylint.lint import PyLinter
27class _ConfigurationFileParser:
28 """Class to parse various formats of configuration files."""
30 def __init__(self, verbose: bool, linter: PyLinter) -> None:
31 self.verbose_mode = verbose
32 self.linter = linter
34 @staticmethod
35 def _parse_ini_file(file_path: Path) -> tuple[dict[str, str], list[str]]:
36 """Parse and handle errors of a ini configuration file."""
37 parser = configparser.ConfigParser(inline_comment_prefixes=("#", ";"))
39 # Use this encoding in order to strip the BOM marker, if any.
40 with open(file_path, encoding="utf_8_sig") as fp:
41 parser.read_file(fp)
43 config_content: dict[str, str] = {}
44 options: list[str] = []
45 for section in parser.sections():
46 if "setup.cfg" in str(file_path) and not section.startswith("pylint"):
47 if section.lower() == "master":
48 # TODO: 3.0: Remove deprecated handling of master, only allow 'pylint.' sections
49 warnings.warn(
50 "The use of 'MASTER' or 'master' as configuration section for pylint "
51 "has been deprecated, as it's bad practice to not start sections titles with the "
52 "tool name. Please use 'pylint.main' instead.",
53 UserWarning,
54 )
55 else:
56 continue
57 for opt, value in parser[section].items():
58 value = value.replace("\n", "")
59 config_content[opt] = value
60 options += [f"--{opt}", value]
61 return config_content, options
63 def _parse_toml_file(self, file_path: Path) -> tuple[dict[str, str], list[str]]:
64 """Parse and handle errors of a toml configuration file."""
65 try:
66 with open(file_path, mode="rb") as fp:
67 content = tomllib.load(fp)
68 except tomllib.TOMLDecodeError as e:
69 self.linter.add_message("config-parse-error", line=0, args=str(e))
70 return {}, []
72 try:
73 sections_values = content["tool"]["pylint"]
74 except KeyError:
75 return {}, []
77 config_content: dict[str, str] = {}
78 options: list[str] = []
79 for opt, values in sections_values.items():
80 if isinstance(values, dict):
81 for config, value in values.items():
82 value = _parse_rich_type_value(value)
83 config_content[config] = value
84 options += [f"--{config}", value]
85 else:
86 values = _parse_rich_type_value(values)
87 config_content[opt] = values
88 options += [f"--{opt}", values]
89 return config_content, options
91 def parse_config_file(
92 self, file_path: Path | None
93 ) -> tuple[dict[str, str], list[str]]:
94 """Parse a config file and return str-str pairs."""
95 if file_path is None:
96 if self.verbose_mode:
97 print(
98 "No config file found, using default configuration", file=sys.stderr
99 )
100 return {}, []
102 file_path = Path(os.path.expandvars(file_path)).expanduser()
103 if not file_path.exists():
104 raise OSError(f"The config file {file_path} doesn't exist!")
106 if self.verbose_mode:
107 print(f"Using config file {file_path}", file=sys.stderr)
109 try:
110 if file_path.suffix == ".toml":
111 return self._parse_toml_file(file_path)
112 return self._parse_ini_file(file_path)
113 except (configparser.Error, tomllib.TOMLDecodeError) as e:
114 self.linter.add_message("config-parse-error", line=0, args=str(e))
115 return {}, []