2
2
3
3
import copy
4
4
import itertools
5
+ import operator
5
6
import os
6
7
from collections import namedtuple
7
8
from collections .abc import Set
8
- from configparser import RawConfigParser
9
+ from configparser import NoOptionError , NoSectionError , RawConfigParser
10
+ from functools import reduce
9
11
from re import compile as re
10
12
11
13
from .utils import __version__ , log
12
14
from .violations import ErrorRegistry , conventions
13
15
16
+ try :
17
+ import toml
18
+ except ImportError : # pragma: no cover
19
+ toml = None # type: ignore
20
+
14
21
15
22
def check_initialized (method ):
16
23
"""Check that the configuration object was initialized."""
@@ -23,6 +30,109 @@ def _decorator(self, *args, **kwargs):
23
30
return _decorator
24
31
25
32
33
+ class TomlParser :
34
+ """ConfigParser that partially mimics RawConfigParser but for toml files.
35
+
36
+ See RawConfigParser for more info. Also, please note that not all
37
+ RawConfigParser functionality is implemented, but only the subset that is
38
+ currently used by pydocstyle.
39
+ """
40
+
41
+ def __init__ (self ):
42
+ """Create a toml parser."""
43
+ self ._config = {}
44
+
45
+ def read (self , filenames , encoding = None ):
46
+ """Read and parse a filename or an iterable of filenames.
47
+
48
+ Files that cannot be opened are silently ignored; this is
49
+ designed so that you can specify an iterable of potential
50
+ configuration file locations (e.g. current directory, user's
51
+ home directory, systemwide directory), and all existing
52
+ configuration files in the iterable will be read. A single
53
+ filename may also be given.
54
+
55
+ Return list of successfully read files.
56
+ """
57
+ if isinstance (filenames , (str , bytes , os .PathLike )):
58
+ filenames = [filenames ]
59
+ read_ok = []
60
+ for filename in filenames :
61
+ try :
62
+ with open (filename , encoding = encoding ) as fp :
63
+ if not toml :
64
+ log .warning (
65
+ "The %s configuration file was ignored, "
66
+ "because the `toml` package is not installed." ,
67
+ filename ,
68
+ )
69
+ continue
70
+ self ._config .update (toml .load (fp ))
71
+ except OSError :
72
+ continue
73
+ if isinstance (filename , os .PathLike ):
74
+ filename = os .fspath (filename )
75
+ read_ok .append (filename )
76
+ return read_ok
77
+
78
+ def _get_section (self , section , allow_none = False ):
79
+ try :
80
+ current = reduce (
81
+ operator .getitem ,
82
+ section .split ('.' ),
83
+ self ._config ['tool' ],
84
+ )
85
+ except KeyError :
86
+ current = None
87
+
88
+ if isinstance (current , dict ):
89
+ return current
90
+ elif allow_none :
91
+ return None
92
+ else :
93
+ raise NoSectionError (section )
94
+
95
+ def has_section (self , section ):
96
+ """Indicate whether the named section is present in the configuration."""
97
+ return self ._get_section (section , allow_none = True ) is not None
98
+
99
+ def options (self , section ):
100
+ """Return a list of option names for the given section name."""
101
+ current = self ._get_section (section )
102
+ return list (current .keys ())
103
+
104
+ def get (self , section , option , * , _conv = None ):
105
+ """Get an option value for a given section."""
106
+ d = self ._get_section (section )
107
+ option = option .lower ()
108
+ try :
109
+ value = d [option ]
110
+ except KeyError :
111
+ raise NoOptionError (option , section )
112
+
113
+ if isinstance (value , dict ):
114
+ raise TypeError (
115
+ f"Expected { section } .{ option } to be an option, not a section."
116
+ )
117
+
118
+ # toml should convert types automatically
119
+ # don't manually convert, just check, that the type is correct
120
+ if _conv is not None and not isinstance (value , _conv ):
121
+ raise TypeError (
122
+ f"The type of { section } .{ option } should be { _conv } "
123
+ )
124
+
125
+ return value
126
+
127
+ def getboolean (self , section , option ):
128
+ """Get a boolean option value for a given section."""
129
+ return self .get (section , option , _conv = bool )
130
+
131
+ def getint (self , section , option ):
132
+ """Get an integer option value for a given section."""
133
+ return self .get (section , option , _conv = int )
134
+
135
+
26
136
class ConfigurationParser :
27
137
"""Responsible for parsing configuration from files and CLI.
28
138
@@ -85,6 +195,7 @@ class ConfigurationParser:
85
195
'.pydocstyle.ini' ,
86
196
'.pydocstylerc' ,
87
197
'.pydocstylerc.ini' ,
198
+ 'pyproject.toml' ,
88
199
# The following is deprecated, but remains for backwards compatibility.
89
200
'.pep257' ,
90
201
)
@@ -310,7 +421,10 @@ def _read_configuration_file(self, path):
310
421
Returns (options, should_inherit).
311
422
312
423
"""
313
- parser = RawConfigParser (inline_comment_prefixes = ('#' , ';' ))
424
+ if path .endswith ('.toml' ):
425
+ parser = TomlParser ()
426
+ else :
427
+ parser = RawConfigParser (inline_comment_prefixes = ('#' , ';' ))
314
428
options = None
315
429
should_inherit = True
316
430
@@ -433,7 +547,10 @@ def _get_config_file_in_folder(cls, path):
433
547
path = os .path .dirname (path )
434
548
435
549
for fn in cls .PROJECT_CONFIG_FILES :
436
- config = RawConfigParser ()
550
+ if fn .endswith ('.toml' ):
551
+ config = TomlParser ()
552
+ else :
553
+ config = RawConfigParser (inline_comment_prefixes = ('#' , ';' ))
437
554
full_path = os .path .join (path , fn )
438
555
if config .read (full_path ) and cls ._get_section_name (config ):
439
556
return full_path
@@ -552,8 +669,10 @@ def _get_set(value_str):
552
669
file.
553
670
554
671
"""
672
+ if isinstance (value_str , str ):
673
+ value_str = value_str .split ("," )
555
674
return cls ._expand_error_codes (
556
- {x .strip () for x in value_str . split ( "," ) } - {"" }
675
+ {x .strip () for x in value_str } - {"" }
557
676
)
558
677
559
678
for opt in optional_set_options :
0 commit comments