• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

intel / mfd-code-quality / 19666991661

25 Nov 2025 10:51AM UTC coverage: 22.331% (-0.2%) from 22.529%
19666991661

Pull #17

github

web-flow
Merge a0d296676 into d461ab6f1
Pull Request #17: feat: UV venv support.

12 of 13 new or added lines in 1 file covered. (92.31%)

66 existing lines in 5 files now uncovered.

477 of 2136 relevant lines covered (22.33%)

0.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.64
/mfd_code_quality/code_standard/configure.py
1
# Copyright (C) 2025 Intel Corporation
2
# SPDX-License-Identifier: MIT
3
"""
4✔
4
Configure coding standard files.
5

6
What script does:
7
- copy .pre-commit-config.yaml
8
- create pyproject.toml basing on generic configuration from generic_pyproject.toml and custom pyproject.toml file in
9
target repository
10
- create ruff.toml basing on generic configuration from generic_ruff.toml and custom ruff.toml file in target
11
repository
12
- install pre-commit hooks.
13

14
Script is made to be run from repository's root directory because .pre-commit-config.yaml, pyproject.toml and ruff.toml
15
file must be placed there.
16
"""
17

18
import logging
4✔
19
import os
4✔
20
import pathlib
4✔
21
import re
4✔
22
from codecs import open as codec_open
4✔
23

24
from jinja2 import Template
4✔
25

26
from mfd_code_quality.utils import set_up_logging, get_root_dir, get_package_name
4✔
27

28
logger = logging.getLogger("mfd-code-quality.configure")
4✔
29

30

31
class ToolConfig:
4✔
32
    """Class for .toml options."""
33

34
    def __init__(self, tool_name: str):
4✔
35
        """Initialize ToolConfig."""
36
        self.tool_name = tool_name
4✔
37
        self.tool_options: dict[str, str] = {}
4✔
38

39
    def __eq__(self, other: "ToolConfig"):
4✔
40
        return self.tool_name.strip() == other.tool_name.strip()
4✔
41

42

43
def get_module_list() -> list[str]:
4✔
44
    """
45
    Get list of modules with configs per module.
46

47
    Read "config_per_module" directory file and return list of modules.
48
    Files contains <module_name>_<config_name> format.
49
    List should contain unique module names.
50

51
    :return: List of modules with configs per module.
52
    """
53
    possible_config_suffix = ["_pyproject.toml", "_ruff.toml"]
4✔
54
    _config_per_module_list = []
4✔
55
    config_per_module_dir = pathlib.Path(os.path.abspath(os.path.dirname(__file__)), "config_per_module")
4✔
56
    for file in config_per_module_dir.iterdir():
4✔
57
        if file.is_file() and any(file.name.endswith(suffix) for suffix in possible_config_suffix):
4✔
58
            for suffix in possible_config_suffix:
4✔
59
                module_name = file.name.replace(suffix, "")
4✔
60
            if module_name not in _config_per_module_list:
4✔
61
                _config_per_module_list.append(module_name)
4✔
62
    return _config_per_module_list
4✔
63

64

65
config_per_module_list = get_module_list()
4✔
66

67

68
def _read_config_content(config_file_path: pathlib.Path) -> list[ToolConfig]:
4✔
69
    """
70
    Read config file content.
71

72
    :param config_file_path: .toml file path.
73
    :return: List of ToolConfig.
74
    """
75
    logger.debug(f"Read content of config file: {config_file_path}")
4✔
76

77
    tool_config_list = []
4✔
78
    tool_config = None
4✔
79
    last_option = None
4✔
80

81
    option_pattern = r"(?P<option_name>.*)(?P<option_value>\s*=\s*.*\s*)"
4✔
82
    with codec_open(str(config_file_path), "r", "utf-8") as f:
4✔
83
        check_ruff_header = True
4✔
84
        for line in f:
4✔
85
            if (
4✔
86
                check_ruff_header
87
                and re.search(option_pattern, line)
88
                and (config_file_path.name.endswith("ruff.toml") or config_file_path.name == "generic_ruff.txt")
89
            ):
90
                # This is necessary to use current mechanism for substitution because ruff.toml config does not
91
                # have this section
92
                # Created ruff.toml file will not have this header
93
                tool_config = ToolConfig("[ruff]\n")
×
94
                tool_config_list.append(tool_config)
×
95
                check_ruff_header = False
×
96
            if line.startswith("#") or line.isspace():
4✔
97
                continue
4✔
98
            elif line.startswith("["):
4✔
99
                tool_config = ToolConfig(line)
4✔
100
                tool_config_list.append(tool_config)
4✔
101
                last_option = None
4✔
102
                check_ruff_header = False
4✔
103
            elif match := re.search(option_pattern, line):
4✔
104
                if tool_config is None:
4✔
105
                    continue
×
106
                tool_config.tool_options[match.group("option_name")] = match.group("option_value")
4✔
107
                last_option = match.group("option_name")
4✔
108
            else:
109
                if tool_config is not None and last_option is not None:
4✔
110
                    tool_config.tool_options[last_option] += line
4✔
111
    return tool_config_list
4✔
112

113

114
def _get_module_name(destination_path: pathlib.Path) -> str:
4✔
115
    """
116
    Get Python package name.
117

118
    :param destination_path: Repository's root directory.
119
    :return: Python package name.
120
    :raises Exception: When Python package name couldn't be found.
121
    """
122
    if any(re.match(r"{{.+}}", file.name) for file in destination_path.iterdir()):  # cookiecutter template
4✔
123
        return _get_template_repo_name(destination_path)
4✔
124

125
    return get_package_name(destination_path)
4✔
126

127

128
def _get_template_repo_name(destination_path: pathlib.Path) -> str:
4✔
129
    """
130
    Get template repository name from root .txt file.
131

132
    :param destination_path: Repository's root directory.
133
    :return: Template repository name.
134
    :raises Exception: When template repository name couldn't be found.
135
    """
136
    logger.debug("Looking for .txt with repository name.")
4✔
137
    repo_name_file = pathlib.Path(destination_path, "repo_name.txt")
4✔
138
    if repo_name_file.exists():
4✔
139
        repo_name = repo_name_file.read_text().strip()
4✔
140
        logger.debug(f"Repository name is: {repo_name}")
4✔
141
        return repo_name
4✔
142

143
    logger.debug(f"Repo name not found in {destination_path}")
×
144
    raise Exception("Script was probably not run in template repository!")
×
145

146

147
def _create_unified_tool_config_list(tool_config_lists: list[list[ToolConfig]]) -> list[ToolConfig]:
4✔
148
    """
149
    Create unified list with tool configs from generic and custom .toml files.
150

151
    :param tool_config_lists: List of lists that contains tool configs from custom and generic .toml file
152
    :return: Unified list with tool configs from generic and custom .toml files
153
    """
154
    unified_tool_config_list = []
4✔
155
    for tool_config_list in tool_config_lists:
4✔
156
        for tool_config in tool_config_list:
4✔
157
            if tool_config not in unified_tool_config_list:
4✔
158
                unified_tool_config_list.append(tool_config)
4✔
159
            else:
160
                index = unified_tool_config_list.index(tool_config)
4✔
161
                unified_tool_config_list[index] = tool_config
4✔
162

163
    return unified_tool_config_list
4✔
164

165

166
def _create_toml_file(unified_tool_config_list: list[ToolConfig], toml_file_path: str) -> None:
4✔
167
    """
168
    Create .toml file basing on unified tool configs from generic and custom .toml files.
169

170
    :param unified_tool_config_list: Unified list with tool configs from generic and custom .toml files
171
    :param toml_file_path: Generated .toml file path
172
    """
173
    logger.debug(f"Create .toml file in path: {toml_file_path}")
4✔
174

175
    with codec_open(toml_file_path, "w", "utf-8") as f:
4✔
176
        first_section = True
4✔
177
        for tool_config in unified_tool_config_list:
4✔
178
            if not tool_config.tool_options:
4✔
179
                continue
×
180
            # ruff.toml forbids to use section [ruff] in config, and we were using it just for the sake of substitution
181
            if not tool_config.tool_name == "[ruff]\n":
4✔
182
                if not first_section:
4✔
183
                    f.write("\n")  # Add a new line before section header except for the first section
4✔
184
                f.write(tool_config.tool_name)
4✔
185
                first_section = False
4✔
186
            for option_name, option_value in tool_config.tool_options.items():
4✔
187
                f.write(option_name)
4✔
188
                if "\n" not in option_value:
4✔
189
                    f.write(f"{option_value}\n")
4✔
190
                else:
191
                    f.write(option_value)
4✔
192

193

194
def _remove_toml_file(toml_file_path: str) -> None:
4✔
195
    """
196
    Remove .toml file.
197

198
    :param toml_file_path: .toml file path
199
    """
200
    logger.debug(f"Remove .toml file in path: {toml_file_path}")
4✔
201
    if os.path.exists(toml_file_path):
4✔
202
        os.remove(toml_file_path)
4✔
203

204

205
def _substitute_toml_file(toml_file_path: str) -> None:
4✔
206
    """
207
    Substitute .toml file with repos specific fields.
208

209
    :param toml_file_path: Generated .toml file path
210
    """
211
    logger.debug(f"Substitute .toml file in path: {toml_file_path}")
4✔
212

213
    with codec_open(toml_file_path, "rt") as f:
4✔
214
        template = Template(f.read())
4✔
215

216
    toml_path = pathlib.Path(toml_file_path)
4✔
217
    module_name = _get_module_name(toml_path.parent)
4✔
218

219
    if "_template" in module_name:
4✔
220
        logger.debug("Template repository, Cookiecutter found in module name, skipping substitution")
×
221
        return
×
222

223
    substitutions = {"module_name": module_name}
4✔
224
    rendered_template = template.render(substitutions)
4✔
225

226
    with codec_open(toml_file_path, "wt") as f:
4✔
227
        f.writelines(rendered_template)
4✔
228

229

230
def create_toml_files(cwd: pathlib.Path, pwd: pathlib.Path, custom_config_name: str, generic_config_name: str) -> None:
4✔
231
    """
232
    Create .toml file using generic and custom configs.
233

234
    :param cwd: Current work directory
235
    :param pwd: Configure.py directory
236
    :param custom_config_name: Custom config name
237
    :param generic_config_name: Generic config name
238
    """
239
    config_lists = []
4✔
240
    custom_config_path = None
4✔
241
    module_name = _get_module_name(cwd)
4✔
242
    repo_config_path = pathlib.Path(cwd, custom_config_name)
4✔
243
    if repo_config_path.is_file():
4✔
244
        logger.debug(f"Repo {custom_config_name} file exists.")
4✔
245
        repo_tool_config_list = _read_config_content(repo_config_path)
4✔
246
        config_lists.append(repo_tool_config_list)
4✔
247

248
    if module_name in config_per_module_list:
4✔
249
        custom_config_path = pathlib.Path(pwd, "config_per_module", f"{module_name}_{custom_config_name}")
4✔
250
        logger.debug(f"Custom {custom_config_name} path: {custom_config_path}")
4✔
251

252
    generic_config_path = pathlib.Path(pwd, generic_config_name)
4✔
253
    logger.debug(f"Generic {generic_config_name} path: {generic_config_path}")
4✔
254

255
    generic_tool_config_list = _read_config_content(generic_config_path)
4✔
256
    config_lists.append(generic_tool_config_list)
4✔
257
    if custom_config_path and custom_config_path.is_file():
4✔
258
        logger.debug(f"Custom {custom_config_name} file exists.")
4✔
259
        custom_ruff_config_list = _read_config_content(custom_config_path)
4✔
260
        config_lists.append(custom_ruff_config_list)
4✔
261

262
    unified_config_list = _create_unified_tool_config_list(config_lists)
4✔
263
    toml_file_path = os.path.join(cwd, custom_config_name)
4✔
264
    _create_toml_file(unified_config_list, toml_file_path)
4✔
265

266
    _substitute_toml_file(toml_file_path)
4✔
267

268

269
def create_config_files() -> None:
4✔
270
    """Create config files pyproject.toml and ruff.toml."""
271
    set_up_logging()
4✔
272
    cwd = get_root_dir()
4✔
273
    pwd = pathlib.Path(os.path.abspath(os.path.dirname(__file__)))
4✔
274

275
    logger.debug("Step 1/2 - Create pyproject.toml file.")
4✔
276
    create_toml_files(cwd, pwd, "pyproject.toml", "generic_pyproject.txt")
4✔
277

278
    logger.debug("Step 2/2 - Create ruff.toml file.")
4✔
279
    create_toml_files(cwd, pwd, "ruff.toml", "generic_ruff.txt")
4✔
280

281

282
def delete_config_files() -> None:
4✔
283
    """Delete config files pyproject.toml and ruff.toml."""
284
    set_up_logging()
4✔
285
    cwd = get_root_dir()
4✔
286
    pwd = pathlib.Path(os.path.abspath(os.path.dirname(__file__)))
4✔
287

288
    logger.debug("Step 1/2 - Remove pyproject.toml")
4✔
289
    cleanup_toml_file(cwd, pwd, "pyproject.toml", "generic_pyproject.txt")
4✔
290

291
    logger.debug("Step 2/2 - Remove ruff.toml")
4✔
292
    _remove_toml_file(os.path.join(cwd, "ruff.toml"))
4✔
293

294

295
def cleanup_toml_file(cwd: pathlib.Path, pwd: pathlib.Path, custom_config_name: str, generic_config_name: str) -> None:
4✔
296
    """
297
    Cleanup toml file.
298

299
    Remove dynamically added sections from toml file.
300

301
    Read generic config content and remove it from the custom toml file.
302
    """
303
    toml_path = pathlib.Path(cwd, custom_config_name)
4✔
304
    generic_config_path = pathlib.Path(pwd, generic_config_name)
4✔
305

306
    if not toml_path.exists() or not generic_config_path.exists():
4✔
307
        logger.debug(f"Cleanup skipped because {custom_config_name} or {generic_config_name} file does not exist.")
4✔
308
        return
4✔
309

310
    with codec_open(generic_config_path, "r", "utf-8") as f:
4✔
311
        generic_content = f.read()
4✔
312

313
    with codec_open(toml_path, "r", "utf-8") as f:
4✔
314
        toml_content = f.read()
4✔
315
    if toml_content == "" or generic_content == "":
4✔
UNCOV
316
        logger.debug(f"Cleanup skipped because {custom_config_name} or {generic_config_name} file is empty.")
2✔
UNCOV
317
        return
2✔
318

319
    generic_content_first_line = generic_content.splitlines()[0]
4✔
320
    toml_content = toml_content.split(generic_content_first_line, 1)[0].rstrip("\r\n")
4✔
321
    with codec_open(toml_path, "w", "utf-8") as f:
4✔
322
        f.write(toml_content)
4✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc