auto-coder 0.1.327__py3-none-any.whl → 0.1.329__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.

Potentially problematic release.


This version of auto-coder might be problematic. Click here for more details.

@@ -0,0 +1,210 @@
1
+ """
2
+ 用于表示代码编译结果的 Pydantic 模型。
3
+ """
4
+
5
+ from enum import Enum
6
+ from typing import List, Dict, Any, Optional, Union
7
+ from datetime import datetime
8
+ from pydantic import BaseModel, Field
9
+
10
+ class CompilationErrorSeverity(str, Enum):
11
+ """编译错误严重性的枚举"""
12
+ ERROR = "error" # 错误
13
+ WARNING = "warning" # 警告
14
+ INFO = "info" # 信息
15
+
16
+ class CompilationErrorPosition(BaseModel):
17
+ """文件中编译错误的位置"""
18
+ line: int = Field(..., description="发现错误的行号(从1开始)")
19
+ column: Optional[int] = Field(None, description="发现错误的列号(从1开始)")
20
+ end_line: Optional[int] = Field(None, description="错误的结束行号")
21
+ end_column: Optional[int] = Field(None, description="错误的结束列号")
22
+
23
+ def to_str(self) -> str:
24
+ """将位置转换为人类可读的字符串"""
25
+ result = f"第 {self.line} 行"
26
+ if self.column is not None:
27
+ result += f",第 {self.column} 列"
28
+ if self.end_line is not None and (self.end_line != self.line or self.end_column is not None):
29
+ result += f" 到第 {self.end_line} 行"
30
+ if self.end_column is not None:
31
+ result += f",第 {self.end_column} 列"
32
+ return result
33
+
34
+ class CompilationError(BaseModel):
35
+ """单个编译错误的表示"""
36
+ code: Optional[str] = Field(None, description="错误代码或标识符")
37
+ message: str = Field(..., description="错误的人类可读描述")
38
+ severity: CompilationErrorSeverity = Field(..., description="错误的严重性级别")
39
+ position: CompilationErrorPosition = Field(..., description="错误在文件中的位置")
40
+ file_path: str = Field(..., description="发现错误的文件路径")
41
+ source: Optional[str] = Field(None, description="发现错误的源代码片段")
42
+
43
+ def to_str(self, show_file_path: bool = True) -> str:
44
+ """
45
+ 将错误转换为人类可读的字符串
46
+
47
+ 参数:
48
+ show_file_path: 是否在输出中包含文件路径
49
+
50
+ 返回:
51
+ 错误的格式化字符串表示
52
+ """
53
+ parts = []
54
+
55
+ if show_file_path:
56
+ parts.append(f"{self.file_path}:{self.position.line}")
57
+
58
+ parts.append(f"[{self.severity.value.upper()}] {self.message}")
59
+
60
+ if self.code:
61
+ parts.append(f"({self.code})")
62
+
63
+ result = " ".join(parts)
64
+
65
+ if self.source:
66
+ # 为了更好的可读性,缩进源代码
67
+ indented_source = "\n ".join(self.source.splitlines())
68
+ result += f"\n {indented_source}"
69
+
70
+ return result
71
+
72
+ class FileCompilationResult(BaseModel):
73
+ """单个文件的编译结果"""
74
+ file_path: str = Field(..., description="被编译的文件路径")
75
+ success: bool = Field(True, description="编译是否成功完成")
76
+ language: str = Field(..., description="被编译的语言/文件类型")
77
+ errors: List[CompilationError] = Field(default_factory=list, description="发现的编译错误列表")
78
+ error_message: Optional[str] = Field(None, description="如果编译失败,错误消息")
79
+ warning_count: int = Field(0, description="发现的警告数量")
80
+ error_count: int = Field(0, description="发现的错误数量")
81
+ info_count: int = Field(0, description="发现的信息性问题数量")
82
+ execution_time_ms: Optional[int] = Field(None, description="编译文件所花费的时间(毫秒)")
83
+ output_file: Optional[str] = Field(None, description="编译输出的文件路径")
84
+
85
+ def to_str(self, show_file_path_in_errors: bool = False) -> str:
86
+ """
87
+ 将文件编译结果转换为人类可读的字符串
88
+
89
+ 参数:
90
+ show_file_path_in_errors: 是否在单个错误中包含文件路径
91
+
92
+ 返回:
93
+ 文件编译结果的格式化字符串表示
94
+ """
95
+ if not self.success:
96
+ return f"编译 {self.file_path} 时出错: {self.error_message or '未知错误'}"
97
+
98
+ result = [f"{self.file_path} 的编译结果 ({self.language}):"]
99
+
100
+ # 摘要信息
101
+ if self.success and self.error_count == 0 and self.warning_count == 0:
102
+ result.append("编译成功")
103
+ if self.output_file:
104
+ result.append(f"输出文件: {self.output_file}")
105
+ else:
106
+ summary = []
107
+ if self.error_count > 0:
108
+ summary.append(f"{self.error_count} 个错误")
109
+ if self.warning_count > 0:
110
+ summary.append(f"{self.warning_count} 个警告")
111
+ if self.info_count > 0:
112
+ summary.append(f"{self.info_count} 个信息")
113
+
114
+ if summary:
115
+ result.append("发现 " + ",".join(summary))
116
+
117
+ if self.execution_time_ms is not None:
118
+ result.append(f"执行时间: {self.execution_time_ms}毫秒")
119
+
120
+ # 添加单个错误
121
+ if self.errors:
122
+ result.append("\n错误:")
123
+ for error in self.errors:
124
+ error_str = error.to_str(show_file_path=show_file_path_in_errors)
125
+ # 缩进错误描述的每一行
126
+ indented_error = "\n ".join(error_str.splitlines())
127
+ result.append(f" {indented_error}")
128
+
129
+ return "\n".join(result)
130
+
131
+ class ProjectCompilationResult(BaseModel):
132
+ """整个项目的编译结果"""
133
+ project_path: str = Field(..., description="被编译的项目路径")
134
+ file_results: Dict[str, FileCompilationResult] = Field(default_factory=dict, description="文件路径到其编译结果的映射")
135
+ total_files: int = Field(0, description="编译的文件总数")
136
+ files_with_errors: int = Field(0, description="至少有一个错误的文件数量")
137
+ total_errors: int = Field(0, description="所有文件中发现的错误总数")
138
+ total_warnings: int = Field(0, description="所有文件中的警告总数")
139
+ total_infos: int = Field(0, description="所有文件中的信息性问题总数")
140
+ success: bool = Field(True, description="整个项目的编译过程是否成功完成")
141
+ error_message: Optional[str] = Field(None, description="如果编译失败,错误消息")
142
+ output_directory: Optional[str] = Field(None, description="编译输出的目录路径")
143
+
144
+ def to_str(self, include_all_files: bool = True, include_errors: bool = True) -> str:
145
+ """
146
+ 将项目编译结果转换为人类可读的字符串
147
+
148
+ 参数:
149
+ include_all_files: 是否包含没有错误的文件的结果
150
+ include_errors: 是否包含单个错误的详细信息
151
+
152
+ 返回:
153
+ 项目编译结果的格式化字符串表示
154
+ """
155
+ result = [f"项目编译结果 ({datetime.now().strftime('%Y-%m-%d %H:%M:%S')})"]
156
+
157
+ result.append(f"项目: {self.project_path}")
158
+
159
+ if not self.success:
160
+ result.append(f"错误: {self.error_message or '未知错误'}")
161
+
162
+ # 摘要信息
163
+ result.append(f"编译的文件: {self.total_files}")
164
+
165
+ if self.success and self.total_errors == 0:
166
+ result.append("编译成功完成")
167
+ if self.output_directory:
168
+ result.append(f"输出目录: {self.output_directory}")
169
+ else:
170
+ result.append(f"有错误的文件: {self.files_with_errors}")
171
+ result.append(f"总错误数: {self.total_errors} ({self.total_errors} 个错误, {self.total_warnings} 个警告, {self.total_infos} 个信息)")
172
+
173
+ # 添加每个文件的结果
174
+ if self.file_results:
175
+ result.append("\n按文件的结果:")
176
+
177
+ for file_path, file_result in sorted(self.file_results.items()):
178
+ # 如果请求,跳过没有错误的文件
179
+ if not include_all_files and not (file_result.error_count + file_result.warning_count + file_result.info_count) > 0:
180
+ continue
181
+
182
+ if include_errors:
183
+ file_str = file_result.to_str(show_file_path_in_errors=False)
184
+ # 缩进文件结果的每一行
185
+ indented_file = "\n ".join(file_str.splitlines())
186
+ result.append(f" {indented_file}")
187
+ else:
188
+ # 每个文件只有一行摘要
189
+ error_count = file_result.error_count + file_result.warning_count + file_result.info_count
190
+ if error_count > 0:
191
+ result.append(f" {file_path}: {error_count} 个问题 ({file_result.error_count} 个错误, {file_result.warning_count} 个警告)")
192
+ else:
193
+ result.append(f" {file_path}: 编译成功")
194
+
195
+ # 在文件之间添加分隔符以提高可读性
196
+ result.append("")
197
+
198
+ return "\n".join(result)
199
+
200
+ def get_file_result(self, file_path: str) -> Optional[FileCompilationResult]:
201
+ """
202
+ 获取特定文件的编译结果
203
+
204
+ 参数:
205
+ file_path: 文件路径
206
+
207
+ 返回:
208
+ 文件的编译结果,如果文件未被编译则返回None
209
+ """
210
+ return self.file_results.get(file_path)
@@ -0,0 +1,343 @@
1
+ """
2
+ Module for compiling code based on a provided YAML configuration.
3
+ This module reads from a compiler.yml file and runs the specified compilation command.
4
+ """
5
+
6
+ import os
7
+ import subprocess
8
+ import re
9
+ import yaml
10
+ from typing import Dict, List, Any, Optional, Union
11
+ import time
12
+ from pathlib import Path
13
+
14
+ from autocoder.compilers.base_compiler import BaseCompiler
15
+ from autocoder.compilers.models import (
16
+ CompilationError,
17
+ FileCompilationResult,
18
+ ProjectCompilationResult,
19
+ CompilationErrorPosition,
20
+ CompilationErrorSeverity
21
+ )
22
+
23
+
24
+ class ProvidedCompiler(BaseCompiler):
25
+ """
26
+ A compiler that uses a provided YAML configuration to execute compilation commands.
27
+ It reads the configuration, runs the command, and processes the output based on
28
+ extraction patterns defined in the YAML.
29
+ """
30
+
31
+ def __init__(self, verbose: bool = False, config_path: Optional[str] = None):
32
+ """
33
+ Initialize the ProvidedCompiler.
34
+
35
+ Args:
36
+ verbose (bool): Whether to display verbose output.
37
+ config_path (Optional[str]): Path to the compiler.yml file. If None, will look in .auto-coder/projects/compiler.yml
38
+ """
39
+ super().__init__(verbose)
40
+ self.config_path = config_path or os.path.join(".auto-coder","projects","compiler.yml")
41
+ self.config = None
42
+
43
+ def get_supported_extensions(self) -> List[str]:
44
+ """
45
+ Get the list of file extensions supported by this compiler.
46
+ This is a generic compiler, so it returns an empty list as it doesn't target specific file types.
47
+
48
+ Returns:
49
+ List[str]: List of supported file extensions.
50
+ """
51
+ return []
52
+
53
+ def _check_dependencies(self) -> bool:
54
+ """
55
+ Check if required dependencies are installed.
56
+ For the provided compiler, we check if the compiler.yml file exists.
57
+
58
+ Returns:
59
+ bool: True if all dependencies are available, False otherwise.
60
+ """
61
+ return os.path.exists(self.config_path)
62
+
63
+ def _load_config(self) -> bool:
64
+ """
65
+ Load the compiler configuration from YAML file.
66
+
67
+ Returns:
68
+ bool: True if configuration was loaded successfully, False otherwise.
69
+ """
70
+ if not self._check_dependencies():
71
+ if self.verbose:
72
+ print(f"Configuration file not found: {self.config_path}")
73
+ return False
74
+
75
+ try:
76
+ with open(self.config_path, 'r', encoding='utf-8') as f:
77
+ self.config = yaml.safe_load(f)
78
+
79
+ if not self.config or 'compilers' not in self.config:
80
+ if self.verbose:
81
+ print(f"Invalid configuration file: 'compilers' section not found")
82
+ return False
83
+
84
+ return True
85
+ except Exception as e:
86
+ if self.verbose:
87
+ print(f"Error loading configuration: {str(e)}")
88
+ return False
89
+
90
+ def _run_compilation_command(self, project_path:str, compiler_config: Dict[str, Any]) -> Dict[str, Any]:
91
+ """
92
+ Run the compilation command specified in the configuration.
93
+
94
+ Args:
95
+ compiler_config (Dict[str, Any]): Compiler configuration from YAML.
96
+
97
+ Returns:
98
+ Dict[str, Any]: Dictionary containing compilation results.
99
+ """
100
+ result = {
101
+ 'success': True,
102
+ 'errors': [],
103
+ 'error_count': 0,
104
+ 'warning_count': 0,
105
+ 'info_count': 0,
106
+ 'raw_output': "",
107
+ }
108
+
109
+ try:
110
+ working_dir = os.path.join(project_path,compiler_config.get('working_dir', '.'))
111
+ command = compiler_config.get('command')
112
+ args = compiler_config.get('args', [])
113
+ extract_regex = compiler_config.get('extract_regex')
114
+
115
+ if not command:
116
+ result['success'] = False
117
+ result['error_message'] = "No command specified in configuration"
118
+ return result
119
+
120
+ # Create the full command with arguments
121
+ full_command = [command] + args
122
+
123
+ # Record start time
124
+ start_time = time.time()
125
+
126
+ # Run the command
127
+ try:
128
+ # Ensure the working directory exists
129
+ os.makedirs(os.path.abspath(working_dir), exist_ok=True)
130
+
131
+ # Run the command in the specified working directory
132
+ process = subprocess.run(
133
+ full_command,
134
+ stdout=subprocess.PIPE,
135
+ stderr=subprocess.PIPE,
136
+ text=True,
137
+ cwd=working_dir
138
+ )
139
+
140
+ # Combine stdout and stderr
141
+ output = process.stdout + process.stderr
142
+ result['raw_output'] = output
143
+
144
+ # Set success based on return code
145
+ result['success'] = process.returncode == 0
146
+
147
+ # If unsuccessful and no error message is set
148
+ if not result['success'] and not result.get('error_message'):
149
+ result['error_message'] = f"Command failed with exit code {process.returncode}"
150
+
151
+ # Extract errors using regex if provided
152
+ if extract_regex and output:
153
+ errors = self._extract_errors(
154
+ output, extract_regex, working_dir)
155
+ result['errors'] = errors
156
+ result['error_count'] = sum(
157
+ 1 for e in errors if e.severity == CompilationErrorSeverity.ERROR)
158
+ result['warning_count'] = sum(
159
+ 1 for e in errors if e.severity == CompilationErrorSeverity.WARNING)
160
+ result['info_count'] = sum(
161
+ 1 for e in errors if e.severity == CompilationErrorSeverity.INFO)
162
+ elif not result['success']:
163
+ result['errors'] = [CompilationError(
164
+ file_path="",
165
+ message=result['raw_output'],
166
+ severity=CompilationErrorSeverity.ERROR,
167
+ position=CompilationErrorPosition(
168
+ line=-1
169
+ ))]
170
+ except subprocess.SubprocessError as e:
171
+ result['success'] = False
172
+ result['error_message'] = f"Error executing command: {str(e)}"
173
+
174
+ # Calculate execution time
175
+ result['execution_time_ms'] = int(
176
+ (time.time() - start_time) * 1000)
177
+
178
+ except Exception as e:
179
+ result['success'] = False
180
+ result['error_message'] = f"Unexpected error: {str(e)}"
181
+
182
+ return result
183
+
184
+ def _extract_errors(self, output: str, regex_pattern: str, base_path: str) -> List[CompilationError]:
185
+ """
186
+ Extract errors from command output using the provided regex pattern.
187
+
188
+ Args:
189
+ output (str): Command output text.
190
+ regex_pattern (str): Regular expression to extract errors.
191
+ base_path (str): Base path for file references.
192
+
193
+ Returns:
194
+ List[CompilationError]: List of extracted compilation errors.
195
+ """
196
+ errors = []
197
+
198
+ try:
199
+ pattern = re.compile(regex_pattern)
200
+ matches = pattern.finditer(output)
201
+
202
+ for match in matches:
203
+ match_str = match.group(0)
204
+ errors.append(CompilationError(
205
+ message=match_str,
206
+ severity=CompilationErrorSeverity.ERROR,
207
+ position=CompilationErrorPosition(
208
+ file_path="",
209
+ line=-1,
210
+ column=-1
211
+ )
212
+ ))
213
+ except Exception as e:
214
+ # If regex parsing fails, create a generic error
215
+ if self.verbose:
216
+ print(f"Error parsing regex: {str(e)}")
217
+
218
+ return errors
219
+
220
+ def compile_file(self, file_path: str) -> Dict[str, Any]:
221
+ """
222
+ This compiler doesn't support compiling individual files directly.
223
+
224
+ Args:
225
+ file_path (str): Path to the file to compile.
226
+
227
+ Returns:
228
+ Dict[str, Any]: Error indicating that file compilation is not supported.
229
+ """
230
+ return {
231
+ 'success': False,
232
+ 'error_message': "ProvidedCompiler does not support compiling individual files.",
233
+ 'file_path': file_path,
234
+ 'language': 'unknown'
235
+ }
236
+
237
+ def compile_project(self, project_path: str,target_compiler_name:Optional[str] = None) -> ProjectCompilationResult:
238
+ """
239
+ Compile a project using configuration from compiler.yml.
240
+
241
+ Args:
242
+ project_path (str): Path to the project directory.
243
+
244
+ Returns:
245
+ Dict[str, Any]: Dictionary containing compilation results.
246
+ """
247
+ # Create a project compilation result
248
+ result = ProjectCompilationResult(
249
+ project_path=project_path,
250
+ success=True
251
+ )
252
+
253
+ # Load configuration
254
+ if not self._load_config():
255
+ result.success = False
256
+ result.error_message = f"Failed to load configuration from {self.config_path}"
257
+ return result
258
+
259
+ target_compiler_config = None
260
+ if target_compiler_name:
261
+ for compiler_config in self.config.get('compilers', []):
262
+ if compiler_config.get("name","") == target_compiler_name:
263
+ target_compiler_config = compiler_config
264
+ break
265
+
266
+ if not target_compiler_config:
267
+ result.success = False
268
+ result.error_message = f"Compiler {target_compiler_name} not found in configuration"
269
+ return result
270
+
271
+ if self.verbose:
272
+ print(f"Running compiler: {target_compiler_name}")
273
+
274
+ # Run the compilation command
275
+ compile_result = self._run_compilation_command(project_path, target_compiler_config)
276
+ compile_errors:List[CompilationError] = compile_result['errors']
277
+ error_count = len(compile_errors)
278
+
279
+ result = ProjectCompilationResult(
280
+ project_path=project_path,
281
+ success=compile_result['success'],
282
+ error_message=compile_result['error_message'],
283
+ file_results={
284
+ "1":FileCompilationResult(
285
+ file_path="1",
286
+ language="unknown",
287
+ errors=compile_errors,
288
+ warning_count= 0,
289
+ error_count= error_count,
290
+ info_count=0,
291
+ execution_time_ms=compile_result['execution_time_ms'],
292
+ output_file=None
293
+ )
294
+ },
295
+ total_errors=error_count,
296
+ total_warnings=0,
297
+ total_infos=0,
298
+ total_files= -1 ,
299
+ files_with_errors=error_count
300
+ )
301
+
302
+ return result
303
+
304
+ def format_compile_result(self, compile_result: Union[ProjectCompilationResult, FileCompilationResult]) -> str:
305
+ """
306
+ Format compilation results into a human-readable string.
307
+
308
+ Args:
309
+ compile_result (Dict[str, Any]): The compilation result dictionary.
310
+
311
+ Returns:
312
+ str: A formatted string representation of the compilation results.
313
+ """
314
+ if isinstance(compile_result, dict) and 'project_path' in compile_result:
315
+ # This is a project compilation result
316
+ project_result = ProjectCompilationResult(**compile_result)
317
+ return project_result.to_str()
318
+ elif isinstance(compile_result, dict) and 'file_path' in compile_result:
319
+ # This is a file compilation result
320
+ file_result = FileCompilationResult(**compile_result)
321
+ return file_result.to_str()
322
+ else:
323
+ # For raw dictionary results
324
+ if compile_result.get('success', False):
325
+ return "Compilation successful"
326
+ else:
327
+ return f"Compilation failed: {compile_result.get('error_message', 'Unknown error')}"
328
+
329
+
330
+ def compile_with_provided_config(project_path: str, config_path: Optional[str] = None, verbose: bool = False) -> Dict[str, Any]:
331
+ """
332
+ Utility function to compile a project using the provided configuration.
333
+
334
+ Args:
335
+ project_path (str): Path to the project directory.
336
+ config_path (Optional[str]): Path to the compiler.yml file. If None, will look in .auto-coder/projects/compiler.yml
337
+ verbose (bool): Whether to display verbose output.
338
+
339
+ Returns:
340
+ Dict[str, Any]: A dictionary containing compilation results.
341
+ """
342
+ compiler = ProvidedCompiler(verbose=verbose, config_path=config_path)
343
+ return compiler.compile_project(project_path)