mdlint 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. mdlint/__init__.py +1 -0
  2. mdlint/cli.py +370 -0
  3. mdlint/config.py +223 -0
  4. mdlint/document.py +128 -0
  5. mdlint/linter.py +375 -0
  6. mdlint/output/__init__.py +1 -0
  7. mdlint/output/json.py +45 -0
  8. mdlint/output/terminal.py +119 -0
  9. mdlint/rules/__init__.py +118 -0
  10. mdlint/rules/base.py +73 -0
  11. mdlint/rules/md001.py +171 -0
  12. mdlint/rules/md003.py +223 -0
  13. mdlint/rules/md004.py +214 -0
  14. mdlint/rules/md005.py +246 -0
  15. mdlint/rules/md007.py +214 -0
  16. mdlint/rules/md009.py +125 -0
  17. mdlint/rules/md010.py +116 -0
  18. mdlint/rules/md011.py +136 -0
  19. mdlint/rules/md012.py +147 -0
  20. mdlint/rules/md013.py +279 -0
  21. mdlint/rules/md014.py +144 -0
  22. mdlint/rules/md018.py +112 -0
  23. mdlint/rules/md019.py +107 -0
  24. mdlint/rules/md020.py +139 -0
  25. mdlint/rules/md021.py +142 -0
  26. mdlint/rules/md022.py +223 -0
  27. mdlint/rules/md023.py +138 -0
  28. mdlint/rules/md024.py +145 -0
  29. mdlint/rules/md025.py +98 -0
  30. mdlint/rules/md026.py +147 -0
  31. mdlint/rules/md027.py +93 -0
  32. mdlint/rules/md028.py +149 -0
  33. mdlint/rules/md029.py +285 -0
  34. mdlint/rules/md030.py +224 -0
  35. mdlint/rules/md031.py +156 -0
  36. mdlint/rules/md032.py +164 -0
  37. mdlint/rules/md033.py +173 -0
  38. mdlint/rules/md034.py +157 -0
  39. mdlint/rules/md035.py +132 -0
  40. mdlint/rules/md036.py +168 -0
  41. mdlint/rules/md037.py +230 -0
  42. mdlint/rules/md038.py +249 -0
  43. mdlint/rules/md039.py +202 -0
  44. mdlint/rules/md040.py +141 -0
  45. mdlint/rules/md041.py +164 -0
  46. mdlint/rules/md042.py +149 -0
  47. mdlint/rules/md043.py +196 -0
  48. mdlint/rules/md044.py +237 -0
  49. mdlint/rules/md045.py +187 -0
  50. mdlint/rules/md046.py +177 -0
  51. mdlint/rules/md047.py +73 -0
  52. mdlint/rules/md048.py +173 -0
  53. mdlint/rules/md049.py +275 -0
  54. mdlint/rules/md050.py +265 -0
  55. mdlint/rules/md051.py +354 -0
  56. mdlint/rules/md052.py +166 -0
  57. mdlint/rules/md053.py +190 -0
  58. mdlint/rules/md054.py +266 -0
  59. mdlint/rules/md055.py +273 -0
  60. mdlint/rules/md056.py +132 -0
  61. mdlint/rules/md058.py +133 -0
  62. mdlint/rules/md059.py +217 -0
  63. mdlint/rules/md060.py +626 -0
  64. mdlint/suppression.py +103 -0
  65. mdlint/violation.py +29 -0
  66. mdlint-0.2.0.dist-info/METADATA +234 -0
  67. mdlint-0.2.0.dist-info/RECORD +69 -0
  68. mdlint-0.2.0.dist-info/WHEEL +4 -0
  69. mdlint-0.2.0.dist-info/entry_points.txt +3 -0
mdlint/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.2.0"
mdlint/cli.py ADDED
@@ -0,0 +1,370 @@
1
+ import dataclasses
2
+ import sys
3
+ from pathlib import Path
4
+ from typing import Literal, get_args, get_origin
5
+
6
+ import rich_click as click
7
+ from rich.console import Console, Group
8
+ from rich.markdown import Markdown
9
+ from rich.padding import Padding
10
+ from rich.panel import Panel
11
+ from rich.syntax import Syntax
12
+
13
+ from mdlint import __version__
14
+ from mdlint.config import apply_cli_overrides, load_config
15
+ from mdlint.linter import Linter, LintResult, discover_files
16
+ from mdlint.output.json import format_json
17
+ from mdlint.output.terminal import print_results
18
+ from mdlint.rules import RULE_REGISTRY
19
+
20
+ # Width for rule documentation output and help output
21
+ RULE_DOC_WIDTH = 120
22
+ HELP_CONFIG_WIDTH = 120
23
+ RULE_DOC_INDENT = 2
24
+
25
+ CONTEXT_SETTINGS = {
26
+ "help_option_names": ["-h", "--help"],
27
+ }
28
+
29
+
30
+ @click.group(context_settings=CONTEXT_SETTINGS)
31
+ @click.rich_config(
32
+ help_config=click.RichHelpConfiguration(text_markup="markdown", width=HELP_CONFIG_WIDTH)
33
+ )
34
+ @click.version_option(version=__version__, prog_name="mdlint")
35
+ def cli() -> None:
36
+ """mdlint - A Python Markdown linter."""
37
+
38
+
39
+ @cli.command()
40
+ @click.rich_config(
41
+ help_config=click.RichHelpConfiguration(
42
+ option_groups={
43
+ "mdlint check": [
44
+ {
45
+ "name": "Rule selection",
46
+ "options": ["--select", "--ignore", "--extend-select", "--extend-ignore"],
47
+ },
48
+ {
49
+ "name": "File selection",
50
+ "options": ["--exclude", "--extend-exclude", "--no-ignore"],
51
+ },
52
+ {
53
+ "name": "Output options",
54
+ "options": ["--format", "--fix", "--verbose", "--show-files", "--show-context"],
55
+ },
56
+ ],
57
+ },
58
+ width=HELP_CONFIG_WIDTH,
59
+ )
60
+ )
61
+ @click.argument("paths", nargs=-1, type=click.Path())
62
+ @click.option(
63
+ "--format",
64
+ "-f",
65
+ "output_format",
66
+ type=click.Choice(["terminal", "json"]),
67
+ default="terminal",
68
+ help="Output format.",
69
+ )
70
+ @click.option(
71
+ "--config",
72
+ "-c",
73
+ "config_path",
74
+ type=str,
75
+ default=None,
76
+ help="Path to config file or inline TOML (e.g., MD003.style='atx').",
77
+ )
78
+ @click.option(
79
+ "--select",
80
+ "-s",
81
+ multiple=True,
82
+ help="Rules to run. Replaces any select list in config (repeatable).",
83
+ )
84
+ @click.option(
85
+ "--ignore",
86
+ "-i",
87
+ multiple=True,
88
+ help="Rules to skip. Replaces any ignore list in config (repeatable).",
89
+ )
90
+ @click.option(
91
+ "--extend-select",
92
+ "-S",
93
+ multiple=True,
94
+ help="Rules to add on top of the select list (repeatable).",
95
+ )
96
+ @click.option(
97
+ "--extend-ignore",
98
+ "-I",
99
+ multiple=True,
100
+ help="Rules to add on top of the ignore list (repeatable).",
101
+ )
102
+ @click.option(
103
+ "--exclude",
104
+ multiple=True,
105
+ help="Exclude files/directories from analysis. Replaces config exclude list (repeatable).",
106
+ )
107
+ @click.option(
108
+ "--extend-exclude",
109
+ multiple=True,
110
+ help="Exclude files/directories on top of the config exclude list (repeatable).",
111
+ )
112
+ @click.option(
113
+ "--no-ignore",
114
+ is_flag=True,
115
+ default=False,
116
+ help="Disable .gitignore pattern matching.",
117
+ )
118
+ @click.option(
119
+ "--verbose",
120
+ "-v",
121
+ is_flag=True,
122
+ default=False,
123
+ help="Show files being processed.",
124
+ )
125
+ @click.option(
126
+ "--show-files",
127
+ is_flag=True,
128
+ default=False,
129
+ help="List files that would be linted and exit.",
130
+ )
131
+ @click.option(
132
+ "--show-context",
133
+ is_flag=True,
134
+ default=False,
135
+ help="Show surrounding lines for each violation.",
136
+ )
137
+ @click.option(
138
+ "--fix",
139
+ is_flag=True,
140
+ default=False,
141
+ help="Auto-fix violations where possible.",
142
+ )
143
+ def check(
144
+ paths: tuple[str, ...],
145
+ output_format: str,
146
+ config_path: str | None,
147
+ select: tuple[str, ...],
148
+ ignore: tuple[str, ...],
149
+ extend_select: tuple[str, ...],
150
+ extend_ignore: tuple[str, ...],
151
+ exclude: tuple[str, ...],
152
+ extend_exclude: tuple[str, ...],
153
+ no_ignore: bool,
154
+ verbose: bool,
155
+ show_files: bool,
156
+ show_context: bool,
157
+ fix: bool,
158
+ ) -> None:
159
+ """Lint Markdown files for style violations.
160
+
161
+ PATHS can be files or directories. Use '-' for stdin. If no paths provided, lints current
162
+ directory.
163
+ """
164
+ # Validate CLI options - check for unknown rules
165
+ all_rules = set(select) | set(ignore) | set(extend_select) | set(extend_ignore)
166
+ unknown = all_rules - set(RULE_REGISTRY.keys())
167
+ if unknown:
168
+ rules = ", ".join(sorted(unknown))
169
+ click.echo(f"Error: Unknown rule(s): {rules}", err=True)
170
+ sys.exit(2)
171
+
172
+ # Load configuration
173
+ config = load_config(config_path=config_path)
174
+ config = apply_cli_overrides(
175
+ config,
176
+ select=select,
177
+ ignore=ignore,
178
+ extend_select=extend_select,
179
+ extend_ignore=extend_ignore,
180
+ )
181
+
182
+ if verbose and config.source:
183
+ click.secho(f"Using config: {config.source}", dim=True, err=True)
184
+
185
+ linter = Linter(rule_configs=config.rules, enabled_rules=config.enabled_rules)
186
+ if verbose:
187
+ click.secho(
188
+ f"Linting with these rules enabled: {', '.join(sorted(config.enabled_rules))}",
189
+ dim=True,
190
+ err=True,
191
+ )
192
+
193
+ # Handle stdin
194
+ if len(paths) == 1 and paths[0] == "-":
195
+ content = sys.stdin.read()
196
+ if fix:
197
+ file_result, fixed_content = linter.fix_stdin(content)
198
+ click.echo(fixed_content, nl=False)
199
+ sys.exit(1 if file_result.violations else 0)
200
+ file_result = linter.lint_stdin(content)
201
+ result = LintResult(files=[file_result])
202
+ else:
203
+ # Default to current directory if no paths
204
+ if not paths:
205
+ paths = (".",)
206
+
207
+ path_list = [Path(p) for p in paths]
208
+ for path in path_list:
209
+ if not path.exists():
210
+ click.echo(f"Error: no such file or directory: {path}", err=True)
211
+ sys.exit(2)
212
+
213
+ if exclude:
214
+ exclude_list: list[str] | None = list(exclude)
215
+ else:
216
+ exclude_list = config.exclude.copy() or None
217
+ if extend_exclude:
218
+ if exclude_list is None:
219
+ exclude_list = []
220
+ exclude_list.extend(extend_exclude)
221
+
222
+ if show_files:
223
+ files = discover_files(
224
+ path_list, respect_gitignore=not no_ignore, exclude_patterns=exclude_list
225
+ )
226
+ for file in files:
227
+ click.echo(file)
228
+ sys.exit(0)
229
+
230
+ if verbose:
231
+ click.secho(f"Scanning {len(path_list)} path(s)...", dim=True, err=True)
232
+
233
+ if fix:
234
+ result = linter.fix_paths(
235
+ path_list, respect_gitignore=not no_ignore, exclude_patterns=exclude_list
236
+ )
237
+ else:
238
+ result = linter.lint_paths(
239
+ path_list, respect_gitignore=not no_ignore, exclude_patterns=exclude_list
240
+ )
241
+
242
+ if verbose:
243
+ click.secho(f"Found {result.files_checked} file(s)", dim=True, err=True)
244
+
245
+ # Output results
246
+ if output_format == "json":
247
+ click.echo(format_json(result))
248
+ else:
249
+ print_results(result, show_context=show_context)
250
+
251
+ sys.exit(result.exit_code)
252
+
253
+
254
+ @cli.command()
255
+ @click.argument("rule_id", required=False)
256
+ @click.option(
257
+ "--show-examples",
258
+ "-e",
259
+ is_flag=True,
260
+ default=False,
261
+ help="Show valid/invalid examples for the rule.",
262
+ )
263
+ def rule(rule_id: str | None, show_examples: bool) -> None:
264
+ """Display rule documentation.
265
+
266
+ If RULE_ID is provided, shows detailed documentation for that rule.
267
+ Otherwise, lists all available rules.
268
+ """
269
+ console = Console(width=RULE_DOC_WIDTH)
270
+
271
+ if rule_id is None:
272
+ # List all rules
273
+ console.print("[bold]Available rules:[/bold]\n")
274
+ for rid, rule_class in sorted(RULE_REGISTRY.items()):
275
+ console.print(f" [yellow]{rid}[/yellow] {rule_class.name:20} {rule_class.summary}")
276
+ raise SystemExit(0) # ty doesn't recognize sys.exit(0) as terminating function
277
+
278
+ # Resolve partial rule ID to full ID
279
+ # Accept numeric input (e.g., "1" -> "MD001") or MD-prefixed (case-insensitive)
280
+ if rule_id.isdigit():
281
+ resolved_id = f"MD{int(rule_id):03d}"
282
+ elif rule_id.upper().startswith("MD"):
283
+ resolved_id = rule_id.upper()
284
+ else:
285
+ click.echo(f"Error: Invalid rule ID '{rule_id}'", err=True)
286
+ sys.exit(2)
287
+
288
+ if resolved_id not in RULE_REGISTRY:
289
+ click.echo(f"Error: Unknown rule '{resolved_id}'", err=True)
290
+ sys.exit(2)
291
+
292
+ rule_class = RULE_REGISTRY[resolved_id]
293
+ console.print(f"[bold yellow]{rule_class.id}[/bold yellow]: {rule_class.name}\n")
294
+ console.print(f" {rule_class.summary}\n")
295
+
296
+ if rule_class.description:
297
+ console.print(Padding("[bold]What it does:[/bold]", (0, 0, 0, RULE_DOC_INDENT)))
298
+ console.print(Padding(Markdown(rule_class.description), (0, 0, 1, RULE_DOC_INDENT)))
299
+
300
+ if rule_class.rationale:
301
+ console.print(Padding("[bold]Why is this bad?[/bold]", (0, 0, 0, RULE_DOC_INDENT)))
302
+ console.print(Padding(Markdown(rule_class.rationale), (0, 0, 1, RULE_DOC_INDENT)))
303
+
304
+ config_class = rule_class.config_class
305
+ config_fields = list(dataclasses.fields(config_class))
306
+ if config_fields:
307
+ console.print(" [bold]Configuration:[/bold]")
308
+ for f in config_fields:
309
+ default = f.default if f.default is not dataclasses.MISSING else None
310
+ # Extract metadata
311
+ metadata = f.metadata if f.metadata else {}
312
+ description = metadata.get("description", "")
313
+ option_descriptions = metadata.get("option_descriptions", {})
314
+ # Extract options from Literal types
315
+ field_type = f.type
316
+ options = None
317
+ if get_origin(field_type) is Literal:
318
+ options = get_args(field_type)
319
+ if description:
320
+ console.print(Padding(f"[cyan]{f.name}[/cyan]: {description}", (0, 0, 0, 4)))
321
+ else:
322
+ console.print(Padding(f"[cyan]{f.name}[/cyan]", (0, 0, 0, 4)))
323
+ console.print(Padding(f"default: {default}", (0, 0, 0, 6)))
324
+ if options:
325
+ if option_descriptions:
326
+ console.print(" options:")
327
+ # Calculate max width for alignment
328
+ max_width = max(len(str(o)) for o in options)
329
+ for opt in options:
330
+ opt_desc = option_descriptions.get(opt, "")
331
+ console.print(f" {opt:<{max_width}} - {opt_desc}")
332
+ else:
333
+ options_str = " | ".join(str(o) for o in options)
334
+ console.print(f" options: {options_str}")
335
+ console.print()
336
+
337
+ if not show_examples:
338
+ return
339
+
340
+ panel_width = RULE_DOC_WIDTH - RULE_DOC_INDENT
341
+ panels = []
342
+
343
+ if rule_class.example_invalid:
344
+ syntax = Syntax(rule_class.example_invalid.rstrip(), "markdown", theme="ansi_dark")
345
+ panel = Panel(
346
+ syntax,
347
+ title="[red]Invalid[/red]",
348
+ title_align="left",
349
+ border_style="red",
350
+ width=panel_width,
351
+ )
352
+ panels.append(panel)
353
+
354
+ if rule_class.example_valid:
355
+ syntax = Syntax(rule_class.example_valid.rstrip(), "markdown", theme="ansi_dark")
356
+ panel = Panel(
357
+ syntax,
358
+ title="[green]Valid[/green]",
359
+ title_align="left",
360
+ border_style="green",
361
+ width=panel_width,
362
+ )
363
+ panels.append(panel)
364
+
365
+ if panels:
366
+ console.print(Padding(Group(*panels), (0, 0, 0, RULE_DOC_INDENT)))
367
+
368
+
369
+ if __name__ == "__main__":
370
+ cli()
mdlint/config.py ADDED
@@ -0,0 +1,223 @@
1
+ import sys
2
+ from dataclasses import dataclass, field
3
+ from pathlib import Path
4
+
5
+ if sys.version_info >= (3, 11):
6
+ import tomllib
7
+ else:
8
+ import tomli as tomllib # type: ignore[import-not-found]
9
+
10
+ from mdlint.rules import RULE_REGISTRY
11
+ from mdlint.rules.base import RuleConfig
12
+
13
+
14
+ @dataclass
15
+ class Configuration:
16
+ """Represents the merged configuration from all sources.
17
+
18
+ Attributes:
19
+ rules: Rule ID -> config mapping.
20
+ select: Rules to enable (baseline). Empty means all rules.
21
+ ignore: Rules to disable (subtracted from select).
22
+ source: Path to config file used, or None for defaults.
23
+ """
24
+
25
+ rules: dict[str, RuleConfig] = field(default_factory=dict)
26
+ select: list[str] = field(default_factory=list)
27
+ ignore: list[str] = field(default_factory=list)
28
+ exclude: list[str] = field(default_factory=list)
29
+ source: str | None = None
30
+
31
+ @property
32
+ def enabled_rules(self) -> set[str]:
33
+ """Compute the set of enabled rule IDs.
34
+
35
+ select establishes the baseline (all rules if empty),
36
+ then ignore subtracts from it.
37
+ """
38
+ # Baseline: select list or all registered rules
39
+ if self.select:
40
+ baseline = set(self.select)
41
+ else:
42
+ baseline = set(RULE_REGISTRY.keys())
43
+
44
+ # Subtract ignored rules
45
+ return baseline - set(self.ignore)
46
+
47
+
48
+ def find_config_file(start_dir: Path | None = None) -> Path | None:
49
+ """Search upward for configuration file.
50
+
51
+ Searches for .mdlint.toml first, then pyproject.toml.
52
+
53
+ Args:
54
+ start_dir: Directory to start searching from. Defaults to cwd.
55
+
56
+ Returns:
57
+ Path to config file, or None if not found.
58
+ """
59
+ current = (start_dir or Path.cwd()).resolve()
60
+ while current != current.parent:
61
+ # Check for .mdlint.toml first
62
+ mdlint_config = current / ".mdlint.toml"
63
+ if mdlint_config.is_file():
64
+ return mdlint_config
65
+
66
+ # Check for pyproject.toml with [tool.mdlint] section
67
+ pyproject = current / "pyproject.toml"
68
+ if pyproject.is_file():
69
+ try:
70
+ with pyproject.open("rb") as f:
71
+ data = tomllib.load(f)
72
+ if "tool" in data and "mdlint" in data["tool"]:
73
+ return pyproject
74
+ except (tomllib.TOMLDecodeError, OSError):
75
+ pass
76
+
77
+ current = current.parent
78
+
79
+ return None
80
+
81
+
82
+ def parse_toml_file(path: Path) -> dict:
83
+ """Parse TOML file and extract mdlint configuration.
84
+
85
+ Args:
86
+ path: Path to TOML file.
87
+
88
+ Returns:
89
+ Configuration dictionary.
90
+ """
91
+ with path.open("rb") as f:
92
+ data = tomllib.load(f)
93
+
94
+ # Handle pyproject.toml format
95
+ if path.name == "pyproject.toml":
96
+ return data.get("tool", {}).get("mdlint", {})
97
+
98
+ return data
99
+
100
+
101
+ def parse_inline_config(config_str: str) -> dict:
102
+ """Parse inline TOML configuration string.
103
+
104
+ Args:
105
+ config_str: Inline TOML string (e.g., "MD003.style='atx'").
106
+
107
+ Returns:
108
+ Configuration dictionary.
109
+ """
110
+ # Top-level config keys that should not be wrapped in [rules]
111
+ top_level_keys = {"select", "ignore", "exclude"}
112
+
113
+ # Handle simple key=value format
114
+ if "=" in config_str and not config_str.strip().startswith("["):
115
+ # Convert MD003.style='atx' to [rules.MD003]\nstyle = 'atx'
116
+ key_path, value = config_str.split("=", 1)
117
+ key_path = key_path.strip()
118
+ value = value.strip()
119
+
120
+ if key_path in top_level_keys:
121
+ # Keep as top-level config
122
+ config_str = f"{key_path} = {value}"
123
+ elif "." in key_path:
124
+ rule_id, param = key_path.rsplit(".", 1)
125
+ config_str = f"[rules.{rule_id}]\n{param} = {value}"
126
+ else:
127
+ config_str = f"[rules]\n{key_path} = {value}"
128
+
129
+ return tomllib.loads(config_str)
130
+
131
+
132
+ def build_rule_configs(config_data: dict) -> dict[str, RuleConfig]:
133
+ """Build rule configurations from parsed config data.
134
+
135
+ Args:
136
+ config_data: Parsed configuration dictionary.
137
+
138
+ Returns:
139
+ Mapping of rule ID to RuleConfig.
140
+ """
141
+ rules_data = config_data.get("rules", {})
142
+ return {
143
+ rule_id: rule_class.config_class(**rules_data.get(rule_id, {}))
144
+ for rule_id, rule_class in RULE_REGISTRY.items()
145
+ }
146
+
147
+
148
+ def load_config(config_path: str | None = None) -> Configuration:
149
+ """Load configuration from file or defaults.
150
+
151
+ Args:
152
+ config_path: Explicit config path or inline TOML.
153
+
154
+ Returns:
155
+ Merged Configuration object.
156
+ """
157
+ config_data: dict = {}
158
+ source: str | None = None
159
+
160
+ if config_path:
161
+ path = Path(config_path)
162
+ if path.is_file():
163
+ config_data = parse_toml_file(path)
164
+ source = str(path)
165
+ else:
166
+ # Treat as inline TOML
167
+ config_data = parse_inline_config(config_path)
168
+ source = "<inline>"
169
+ else:
170
+ # Search for config file
171
+ found_path = find_config_file()
172
+ if found_path:
173
+ config_data = parse_toml_file(found_path)
174
+ source = str(found_path)
175
+
176
+ rule_configs = build_rule_configs(config_data)
177
+
178
+ # Extract select/ignore/exclude lists
179
+ select = config_data.get("select", [])
180
+ ignore = config_data.get("ignore", [])
181
+ exclude = config_data.get("exclude", [])
182
+
183
+ return Configuration(
184
+ rules=rule_configs,
185
+ select=select,
186
+ ignore=ignore,
187
+ exclude=exclude,
188
+ source=source,
189
+ )
190
+
191
+
192
+ def apply_cli_overrides(
193
+ config: Configuration,
194
+ select: tuple[str, ...] | None = None,
195
+ ignore: tuple[str, ...] | None = None,
196
+ extend_select: tuple[str, ...] | None = None,
197
+ extend_ignore: tuple[str, ...] | None = None,
198
+ ) -> Configuration:
199
+ """Apply CLI rule selection overrides.
200
+
201
+ Args:
202
+ config: Base configuration.
203
+ select: Replaces the entire select baseline.
204
+ ignore: Replaces the entire ignore list.
205
+ extend_select: Adds to the existing select list.
206
+ extend_ignore: Adds to the existing ignore list.
207
+
208
+ Returns:
209
+ Modified Configuration.
210
+ """
211
+ if select:
212
+ config.select = list(select)
213
+
214
+ if ignore:
215
+ config.ignore = list(ignore)
216
+
217
+ if extend_select:
218
+ config.select.extend(extend_select)
219
+
220
+ if extend_ignore:
221
+ config.ignore.extend(extend_ignore)
222
+
223
+ return config