@@ -109,11 +109,53 @@ class Config(ToDictMixin):
109109 that should be available to all rules.
110110 """
111111
112+ @classmethod
113+ def from_value (cls , value : Any ) -> "Config" :
114+ """Convert given `value` into a `Config` object.
115+
116+ If `value` is already a `Config` then it is returned as-is.
117+
118+ Args:
119+ value: A `Config` object, a `dict` containing the
120+ configuration properties, or `None` which
121+ converts into an empty configuration.
122+ Returns:
123+ A `Config` object.
124+ """
125+ if isinstance (value , Config ):
126+ return value
127+ if value is None :
128+ return Config ()
129+ if not isinstance (value , dict ):
130+ raise TypeError (format_message_type_of ("configuration" , value , "dict" ))
131+ if not value :
132+ return Config ()
133+
134+ files = cls ._parse_pattern_list (value , "files" )
135+ ignores = cls ._parse_pattern_list (value , "ignores" )
136+ linter_options = cls ._parse_options ("linter_options" , value )
137+ opener_options = cls ._parse_options ("opener_options" , value )
138+ processor = cls ._parse_processor (value )
139+ plugins = cls ._parse_plugins (value )
140+ rules = cls ._parse_rules (value )
141+ settings = cls ._parse_options ("settings" , value )
142+
143+ return Config (
144+ name = value .get ("name" ),
145+ files = files ,
146+ ignores = ignores ,
147+ linter_options = linter_options ,
148+ opener_options = opener_options ,
149+ processor = processor ,
150+ plugins = plugins ,
151+ rules = rules ,
152+ settings = settings ,
153+ )
154+
112155 @property
113156 def global_ignores (self ) -> list [str ]:
114- """The list of `ignores` patterns from this configuration which
115- are _global ignores_.
116- """
157+ # The list of `ignores` patterns from this configuration which
158+ # are _global ignores_.
117159 return (
118160 self .ignores
119161 if self .ignores
@@ -147,53 +189,21 @@ def get_rule(self, rule_id: str) -> "Rule":
147189
148190 return rule
149191
150- def merge (self , config_obj : "Config" , name : str = None ) -> "Config" :
192+ def merge (self , config : "Config" , name : str = None ) -> "Config" :
151193 return Config (
152194 name = name ,
153- files = self ._merge_pattern_lists (self .files , config_obj .files ),
154- ignores = self ._merge_pattern_lists (self .ignores , config_obj .ignores ),
195+ files = self ._merge_pattern_lists (self .files , config .files ),
196+ ignores = self ._merge_pattern_lists (self .ignores , config .ignores ),
155197 linter_options = self ._merge_options (
156- self .linter_options , config_obj .linter_options
198+ self .linter_options , config .linter_options
157199 ),
158200 opener_options = self ._merge_options (
159- self .opener_options , config_obj .opener_options
201+ self .opener_options , config .opener_options
160202 ),
161- processor = merge_values (self .processor , config_obj .processor ), # TBD!
162- plugins = self ._merge_plugin_dicts (self .plugins , config_obj .plugins ),
163- rules = self ._merge_rule_dicts (self .rules , config_obj .rules ),
164- settings = self ._merge_options (self .rules , config_obj .settings ),
165- )
166-
167- @classmethod
168- def from_value (cls , value : Any ) -> "Config" :
169- if isinstance (value , Config ):
170- return value
171- if value is None :
172- return Config ()
173- if not isinstance (value , dict ):
174- raise TypeError (format_message_type_of ("configuration" , value , "dict" ))
175- if not value :
176- return Config ()
177-
178- files = cls ._parse_pattern_list (value , "files" )
179- ignores = cls ._parse_pattern_list (value , "ignores" )
180- linter_options = cls ._parse_options ("linter_options" , value )
181- opener_options = cls ._parse_options ("opener_options" , value )
182- processor = cls ._parse_processor (value )
183- plugins = cls ._parse_plugins (value )
184- rules = cls ._parse_rules (value )
185- settings = cls ._parse_options ("settings" , value )
186-
187- return Config (
188- name = value .get ("name" ),
189- files = files ,
190- ignores = ignores ,
191- linter_options = linter_options ,
192- opener_options = opener_options ,
193- processor = processor ,
194- plugins = plugins ,
195- rules = rules ,
196- settings = settings ,
203+ processor = merge_values (self .processor , config .processor ), # TBD!
204+ plugins = self ._merge_plugin_dicts (self .plugins , config .plugins ),
205+ rules = self ._merge_rule_dicts (self .rules , config .rules ),
206+ settings = self ._merge_options (self .settings , config .settings ),
197207 )
198208
199209 @classmethod
@@ -323,9 +333,43 @@ def merge_configs(
323333
324334@dataclass (frozen = True )
325335class ConfigList :
336+ """A holder for a list of `Config` objects."""
337+
326338 configs : list [Config ] = field (default_factory = list )
339+ """The list of `Config` objects."""
340+
341+ @classmethod
342+ def from_value (cls , value : Any ) -> "ConfigList" :
343+ """Convert given `value` into a `ConfigList` object.
344+
345+ If `value` is already a `ConfigList` then it is returned as-is.
327346
328- def resolve_for_path (self , path : str ) -> Config | None :
347+ Args:
348+ value: A `ConfigList` object or `list` of values which can be
349+ converted into `Config` objects.
350+ Returns:
351+ A `ConfigList` object.
352+ """
353+ if isinstance (value , ConfigList ):
354+ return value
355+ if isinstance (value , list ):
356+ return ConfigList ([Config .from_value (c ) for c in value ])
357+ raise TypeError (
358+ format_message_type_of (
359+ "configuration list" , value , "ConfigList|list[Config|dict]"
360+ )
361+ )
362+
363+ def compute_config (self , file_path : str ) -> Config | None :
364+ """Compute the configuration object for the given file path.
365+
366+ Args:
367+ file_path: A dataset file path.
368+ Returns:
369+ A `Config` object which may be empty, or `None`
370+ if `file_path` is not included by any `files` pattern
371+ or intentionally ignored by global `ignores`.
372+ """
329373 # Step 1: Check against global ignores
330374 global_ignores = set ()
331375 effective_configs = []
@@ -336,37 +380,37 @@ def resolve_for_path(self, path: str) -> Config | None:
336380 else :
337381 effective_configs .append (c )
338382 for p in global_ignores :
339- if fnmatch .fnmatch (path , p ):
383+ if fnmatch .fnmatch (file_path , p ):
340384 return None
341385
342386 # Step 2: Check against global ignores
343- config = Config ()
387+ config = None
344388 for c in effective_configs :
345- matches = True
389+ excluded = False
346390 if c .ignores :
347391 for p in c .ignores :
348- matches = fnmatch .fnmatch (path , p )
349- if not matches :
392+ excluded = fnmatch .fnmatch (file_path , p )
393+ if excluded :
350394 break
351- if matches :
395+ included = not excluded
396+ if included :
352397 if c .files :
353398 for p in c .files :
354- matches = fnmatch .fnmatch (path , p )
355- if matches :
399+ included = fnmatch .fnmatch (file_path , p )
400+ if not included :
356401 break
357- if matches :
358- config = config .merge (c )
402+ if included :
403+ config = config .merge (c ) if config is not None else c
359404
360- return config
361-
362- @classmethod
363- def from_value (cls , value : Any ) -> "ConfigList" :
364- if isinstance (value , ConfigList ):
365- return value
366- if isinstance (value , list ):
367- return ConfigList ([Config .from_value (c ) for c in value ])
368- raise TypeError (
369- format_message_type_of (
370- "configuration list" , value , "ConfigList|list[Config|dict]"
371- )
405+ if config is None :
406+ return None
407+ # Exclude "files" and "ignores" because they have been used
408+ return Config (
409+ name = "<computed>" ,
410+ linter_options = config .linter_options ,
411+ opener_options = config .opener_options ,
412+ processor = config .processor ,
413+ plugins = config .plugins ,
414+ rules = config .rules ,
415+ settings = config .settings ,
372416 )
0 commit comments