tree-sitter-analyzer 1.9.17.1__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.
- tree_sitter_analyzer/__init__.py +132 -0
- tree_sitter_analyzer/__main__.py +11 -0
- tree_sitter_analyzer/api.py +853 -0
- tree_sitter_analyzer/cli/__init__.py +39 -0
- tree_sitter_analyzer/cli/__main__.py +12 -0
- tree_sitter_analyzer/cli/argument_validator.py +89 -0
- tree_sitter_analyzer/cli/commands/__init__.py +26 -0
- tree_sitter_analyzer/cli/commands/advanced_command.py +226 -0
- tree_sitter_analyzer/cli/commands/base_command.py +181 -0
- tree_sitter_analyzer/cli/commands/default_command.py +18 -0
- tree_sitter_analyzer/cli/commands/find_and_grep_cli.py +188 -0
- tree_sitter_analyzer/cli/commands/list_files_cli.py +133 -0
- tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -0
- tree_sitter_analyzer/cli/commands/query_command.py +109 -0
- tree_sitter_analyzer/cli/commands/search_content_cli.py +161 -0
- tree_sitter_analyzer/cli/commands/structure_command.py +156 -0
- tree_sitter_analyzer/cli/commands/summary_command.py +116 -0
- tree_sitter_analyzer/cli/commands/table_command.py +414 -0
- tree_sitter_analyzer/cli/info_commands.py +124 -0
- tree_sitter_analyzer/cli_main.py +472 -0
- tree_sitter_analyzer/constants.py +85 -0
- tree_sitter_analyzer/core/__init__.py +15 -0
- tree_sitter_analyzer/core/analysis_engine.py +580 -0
- tree_sitter_analyzer/core/cache_service.py +333 -0
- tree_sitter_analyzer/core/engine.py +585 -0
- tree_sitter_analyzer/core/parser.py +293 -0
- tree_sitter_analyzer/core/query.py +605 -0
- tree_sitter_analyzer/core/query_filter.py +200 -0
- tree_sitter_analyzer/core/query_service.py +340 -0
- tree_sitter_analyzer/encoding_utils.py +530 -0
- tree_sitter_analyzer/exceptions.py +747 -0
- tree_sitter_analyzer/file_handler.py +246 -0
- tree_sitter_analyzer/formatters/__init__.py +1 -0
- tree_sitter_analyzer/formatters/base_formatter.py +201 -0
- tree_sitter_analyzer/formatters/csharp_formatter.py +367 -0
- tree_sitter_analyzer/formatters/formatter_config.py +197 -0
- tree_sitter_analyzer/formatters/formatter_factory.py +84 -0
- tree_sitter_analyzer/formatters/formatter_registry.py +377 -0
- tree_sitter_analyzer/formatters/formatter_selector.py +96 -0
- tree_sitter_analyzer/formatters/go_formatter.py +368 -0
- tree_sitter_analyzer/formatters/html_formatter.py +498 -0
- tree_sitter_analyzer/formatters/java_formatter.py +423 -0
- tree_sitter_analyzer/formatters/javascript_formatter.py +611 -0
- tree_sitter_analyzer/formatters/kotlin_formatter.py +268 -0
- tree_sitter_analyzer/formatters/language_formatter_factory.py +123 -0
- tree_sitter_analyzer/formatters/legacy_formatter_adapters.py +228 -0
- tree_sitter_analyzer/formatters/markdown_formatter.py +725 -0
- tree_sitter_analyzer/formatters/php_formatter.py +301 -0
- tree_sitter_analyzer/formatters/python_formatter.py +830 -0
- tree_sitter_analyzer/formatters/ruby_formatter.py +278 -0
- tree_sitter_analyzer/formatters/rust_formatter.py +233 -0
- tree_sitter_analyzer/formatters/sql_formatter_wrapper.py +689 -0
- tree_sitter_analyzer/formatters/sql_formatters.py +536 -0
- tree_sitter_analyzer/formatters/typescript_formatter.py +543 -0
- tree_sitter_analyzer/formatters/yaml_formatter.py +462 -0
- tree_sitter_analyzer/interfaces/__init__.py +9 -0
- tree_sitter_analyzer/interfaces/cli.py +535 -0
- tree_sitter_analyzer/interfaces/cli_adapter.py +359 -0
- tree_sitter_analyzer/interfaces/mcp_adapter.py +224 -0
- tree_sitter_analyzer/interfaces/mcp_server.py +428 -0
- tree_sitter_analyzer/language_detector.py +553 -0
- tree_sitter_analyzer/language_loader.py +271 -0
- tree_sitter_analyzer/languages/__init__.py +10 -0
- tree_sitter_analyzer/languages/csharp_plugin.py +1076 -0
- tree_sitter_analyzer/languages/css_plugin.py +449 -0
- tree_sitter_analyzer/languages/go_plugin.py +836 -0
- tree_sitter_analyzer/languages/html_plugin.py +496 -0
- tree_sitter_analyzer/languages/java_plugin.py +1299 -0
- tree_sitter_analyzer/languages/javascript_plugin.py +1622 -0
- tree_sitter_analyzer/languages/kotlin_plugin.py +656 -0
- tree_sitter_analyzer/languages/markdown_plugin.py +1928 -0
- tree_sitter_analyzer/languages/php_plugin.py +862 -0
- tree_sitter_analyzer/languages/python_plugin.py +1636 -0
- tree_sitter_analyzer/languages/ruby_plugin.py +757 -0
- tree_sitter_analyzer/languages/rust_plugin.py +673 -0
- tree_sitter_analyzer/languages/sql_plugin.py +2444 -0
- tree_sitter_analyzer/languages/typescript_plugin.py +1892 -0
- tree_sitter_analyzer/languages/yaml_plugin.py +695 -0
- tree_sitter_analyzer/legacy_table_formatter.py +860 -0
- tree_sitter_analyzer/mcp/__init__.py +34 -0
- tree_sitter_analyzer/mcp/resources/__init__.py +43 -0
- tree_sitter_analyzer/mcp/resources/code_file_resource.py +208 -0
- tree_sitter_analyzer/mcp/resources/project_stats_resource.py +586 -0
- tree_sitter_analyzer/mcp/server.py +869 -0
- tree_sitter_analyzer/mcp/tools/__init__.py +28 -0
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +779 -0
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +291 -0
- tree_sitter_analyzer/mcp/tools/base_tool.py +139 -0
- tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +816 -0
- tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +686 -0
- tree_sitter_analyzer/mcp/tools/list_files_tool.py +413 -0
- tree_sitter_analyzer/mcp/tools/output_format_validator.py +148 -0
- tree_sitter_analyzer/mcp/tools/query_tool.py +443 -0
- tree_sitter_analyzer/mcp/tools/read_partial_tool.py +464 -0
- tree_sitter_analyzer/mcp/tools/search_content_tool.py +836 -0
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +572 -0
- tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +653 -0
- tree_sitter_analyzer/mcp/utils/__init__.py +113 -0
- tree_sitter_analyzer/mcp/utils/error_handler.py +569 -0
- tree_sitter_analyzer/mcp/utils/file_output_factory.py +217 -0
- tree_sitter_analyzer/mcp/utils/file_output_manager.py +322 -0
- tree_sitter_analyzer/mcp/utils/gitignore_detector.py +358 -0
- tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -0
- tree_sitter_analyzer/mcp/utils/search_cache.py +343 -0
- tree_sitter_analyzer/models.py +840 -0
- tree_sitter_analyzer/mypy_current_errors.txt +2 -0
- tree_sitter_analyzer/output_manager.py +255 -0
- tree_sitter_analyzer/platform_compat/__init__.py +3 -0
- tree_sitter_analyzer/platform_compat/adapter.py +324 -0
- tree_sitter_analyzer/platform_compat/compare.py +224 -0
- tree_sitter_analyzer/platform_compat/detector.py +67 -0
- tree_sitter_analyzer/platform_compat/fixtures.py +228 -0
- tree_sitter_analyzer/platform_compat/profiles.py +217 -0
- tree_sitter_analyzer/platform_compat/record.py +55 -0
- tree_sitter_analyzer/platform_compat/recorder.py +155 -0
- tree_sitter_analyzer/platform_compat/report.py +92 -0
- tree_sitter_analyzer/plugins/__init__.py +280 -0
- tree_sitter_analyzer/plugins/base.py +647 -0
- tree_sitter_analyzer/plugins/manager.py +384 -0
- tree_sitter_analyzer/project_detector.py +328 -0
- tree_sitter_analyzer/queries/__init__.py +27 -0
- tree_sitter_analyzer/queries/csharp.py +216 -0
- tree_sitter_analyzer/queries/css.py +615 -0
- tree_sitter_analyzer/queries/go.py +275 -0
- tree_sitter_analyzer/queries/html.py +543 -0
- tree_sitter_analyzer/queries/java.py +402 -0
- tree_sitter_analyzer/queries/javascript.py +724 -0
- tree_sitter_analyzer/queries/kotlin.py +192 -0
- tree_sitter_analyzer/queries/markdown.py +258 -0
- tree_sitter_analyzer/queries/php.py +95 -0
- tree_sitter_analyzer/queries/python.py +859 -0
- tree_sitter_analyzer/queries/ruby.py +92 -0
- tree_sitter_analyzer/queries/rust.py +223 -0
- tree_sitter_analyzer/queries/sql.py +555 -0
- tree_sitter_analyzer/queries/typescript.py +871 -0
- tree_sitter_analyzer/queries/yaml.py +236 -0
- tree_sitter_analyzer/query_loader.py +272 -0
- tree_sitter_analyzer/security/__init__.py +22 -0
- tree_sitter_analyzer/security/boundary_manager.py +277 -0
- tree_sitter_analyzer/security/regex_checker.py +297 -0
- tree_sitter_analyzer/security/validator.py +599 -0
- tree_sitter_analyzer/table_formatter.py +782 -0
- tree_sitter_analyzer/utils/__init__.py +53 -0
- tree_sitter_analyzer/utils/logging.py +433 -0
- tree_sitter_analyzer/utils/tree_sitter_compat.py +289 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/METADATA +485 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/RECORD +149 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/WHEEL +4 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/entry_points.txt +25 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
File Handler Module
|
|
4
|
+
|
|
5
|
+
This module provides file reading functionality with encoding detection and fallback.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import itertools
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from .encoding_utils import read_file_safe, read_file_safe_streaming
|
|
12
|
+
from .utils import setup_logger
|
|
13
|
+
|
|
14
|
+
# Set up logger for this module
|
|
15
|
+
logger = setup_logger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def log_error(message: str, *args: object, **kwargs: object) -> None:
|
|
19
|
+
"""Log error message"""
|
|
20
|
+
logger.error(message, *args, **kwargs) # type: ignore[arg-type]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def log_info(message: str, *args: object, **kwargs: object) -> None:
|
|
24
|
+
"""Log info message"""
|
|
25
|
+
logger.info(message, *args, **kwargs) # type: ignore[arg-type]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def log_warning(message: str, *args: object, **kwargs: object) -> None:
|
|
29
|
+
"""Log warning message"""
|
|
30
|
+
logger.warning(message, *args, **kwargs) # type: ignore[arg-type]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def detect_language_from_extension(file_path: str) -> str:
|
|
34
|
+
"""
|
|
35
|
+
Detect programming language from file extension
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
file_path: File path to analyze
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Language name or 'unknown' if not recognized
|
|
42
|
+
"""
|
|
43
|
+
extension = Path(file_path).suffix.lower()
|
|
44
|
+
|
|
45
|
+
extension_map = {
|
|
46
|
+
".java": "java",
|
|
47
|
+
".py": "python",
|
|
48
|
+
".js": "javascript",
|
|
49
|
+
".ts": "typescript",
|
|
50
|
+
".cpp": "cpp",
|
|
51
|
+
".c": "c",
|
|
52
|
+
".h": "c",
|
|
53
|
+
".hpp": "cpp",
|
|
54
|
+
".cs": "csharp",
|
|
55
|
+
".go": "go",
|
|
56
|
+
".rs": "rust",
|
|
57
|
+
".rb": "ruby",
|
|
58
|
+
".php": "php",
|
|
59
|
+
".kt": "kotlin",
|
|
60
|
+
".scala": "scala",
|
|
61
|
+
".swift": "swift",
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return extension_map.get(extension, "unknown")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def read_file_with_fallback(file_path: str) -> bytes | None:
|
|
68
|
+
"""
|
|
69
|
+
Read file with encoding fallback using unified encoding utilities
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
file_path: Path to the file to read
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
File content as bytes, or None if file doesn't exist
|
|
76
|
+
"""
|
|
77
|
+
# Check file existence first
|
|
78
|
+
file_obj = Path(file_path)
|
|
79
|
+
if not file_obj.exists():
|
|
80
|
+
log_error(f"File does not exist: {file_path}")
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
content, detected_encoding = read_file_safe(file_path)
|
|
85
|
+
log_info(
|
|
86
|
+
f"Successfully read file {file_path} with encoding: {detected_encoding}"
|
|
87
|
+
)
|
|
88
|
+
return content.encode("utf-8")
|
|
89
|
+
|
|
90
|
+
except Exception as e:
|
|
91
|
+
log_error(f"Failed to read file {file_path}: {e}")
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def read_file_partial(
|
|
96
|
+
file_path: str,
|
|
97
|
+
start_line: int,
|
|
98
|
+
end_line: int | None = None,
|
|
99
|
+
start_column: int | None = None,
|
|
100
|
+
end_column: int | None = None,
|
|
101
|
+
) -> str | None:
|
|
102
|
+
"""
|
|
103
|
+
Read partial file content by line/column range using streaming for memory efficiency.
|
|
104
|
+
|
|
105
|
+
Performance: Uses streaming approach for 150x speedup on large files.
|
|
106
|
+
Only loads requested lines into memory instead of entire file.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
file_path: Path to file
|
|
110
|
+
start_line: Start line (1-based)
|
|
111
|
+
end_line: End line (1-based, None means EOF)
|
|
112
|
+
start_column: Start column (0-based, optional)
|
|
113
|
+
end_column: End column (0-based, optional)
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Selected content string, or None on error
|
|
117
|
+
"""
|
|
118
|
+
# Check file existence
|
|
119
|
+
file_obj = Path(file_path)
|
|
120
|
+
if not file_obj.exists():
|
|
121
|
+
log_error(f"File does not exist: {file_path}")
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
# Parameter validation
|
|
125
|
+
if start_line < 1:
|
|
126
|
+
log_error(f"Invalid start_line: {start_line}. Line numbers start from 1.")
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
if end_line is not None and end_line < start_line:
|
|
130
|
+
log_error(f"Invalid range: end_line ({end_line}) < start_line ({start_line})")
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
# Use streaming approach for memory efficiency
|
|
135
|
+
with read_file_safe_streaming(file_path) as f:
|
|
136
|
+
# Convert to 0-based indexing
|
|
137
|
+
start_idx = start_line - 1
|
|
138
|
+
end_idx = end_line - 1 if end_line is not None else None
|
|
139
|
+
|
|
140
|
+
# Use itertools.islice for efficient line selection
|
|
141
|
+
if end_idx is not None:
|
|
142
|
+
# Read specific range
|
|
143
|
+
selected_lines_iter = itertools.islice(f, start_idx, end_idx + 1)
|
|
144
|
+
else:
|
|
145
|
+
# Read from start_line to end of file
|
|
146
|
+
selected_lines_iter = itertools.islice(f, start_idx, None)
|
|
147
|
+
|
|
148
|
+
# Convert iterator to list for processing
|
|
149
|
+
selected_lines = list(selected_lines_iter)
|
|
150
|
+
|
|
151
|
+
# Check if we got any lines
|
|
152
|
+
if not selected_lines:
|
|
153
|
+
# Check if start_line is beyond file length by counting lines
|
|
154
|
+
with read_file_safe_streaming(file_path) as f_count:
|
|
155
|
+
total_lines = sum(1 for _ in f_count)
|
|
156
|
+
|
|
157
|
+
if start_idx >= total_lines:
|
|
158
|
+
log_warning(
|
|
159
|
+
f"start_line ({start_line}) exceeds file length ({total_lines})"
|
|
160
|
+
)
|
|
161
|
+
return ""
|
|
162
|
+
else:
|
|
163
|
+
# File might be empty or other issue
|
|
164
|
+
return ""
|
|
165
|
+
|
|
166
|
+
# Handle column range if specified
|
|
167
|
+
if start_column is not None or end_column is not None:
|
|
168
|
+
processed_lines = []
|
|
169
|
+
for i, line in enumerate(selected_lines):
|
|
170
|
+
# Strip newline for processing
|
|
171
|
+
line_content = line.rstrip("\r\n")
|
|
172
|
+
|
|
173
|
+
if i == 0 and start_column is not None:
|
|
174
|
+
# First line: apply start_column
|
|
175
|
+
line_content = (
|
|
176
|
+
line_content[start_column:]
|
|
177
|
+
if start_column < len(line_content)
|
|
178
|
+
else ""
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
if i == len(selected_lines) - 1 and end_column is not None:
|
|
182
|
+
# Last line: apply end_column
|
|
183
|
+
if i == 0 and start_column is not None:
|
|
184
|
+
# Single line: apply both start and end columns
|
|
185
|
+
col_end = (
|
|
186
|
+
end_column - start_column
|
|
187
|
+
if end_column >= start_column
|
|
188
|
+
else 0
|
|
189
|
+
)
|
|
190
|
+
line_content = line_content[:col_end] if col_end > 0 else ""
|
|
191
|
+
else:
|
|
192
|
+
line_content = (
|
|
193
|
+
line_content[:end_column]
|
|
194
|
+
if end_column < len(line_content)
|
|
195
|
+
else line_content
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# Preserve original newline (except last line)
|
|
199
|
+
if i < len(selected_lines) - 1:
|
|
200
|
+
# Detect original newline char of the line
|
|
201
|
+
original_line = selected_lines[i]
|
|
202
|
+
if original_line.endswith("\r\n"):
|
|
203
|
+
line_content += "\r\n"
|
|
204
|
+
elif original_line.endswith("\n"):
|
|
205
|
+
line_content += "\n"
|
|
206
|
+
elif original_line.endswith("\r"):
|
|
207
|
+
line_content += "\r"
|
|
208
|
+
|
|
209
|
+
processed_lines.append(line_content)
|
|
210
|
+
|
|
211
|
+
result = "".join(processed_lines)
|
|
212
|
+
else:
|
|
213
|
+
# No column range: join lines directly
|
|
214
|
+
result = "".join(selected_lines)
|
|
215
|
+
|
|
216
|
+
# Calculate end line for logging
|
|
217
|
+
actual_end_line = end_line or (start_line + len(selected_lines) - 1)
|
|
218
|
+
|
|
219
|
+
log_info(
|
|
220
|
+
f"Successfully read partial file {file_path}: "
|
|
221
|
+
f"lines {start_line}-{actual_end_line}"
|
|
222
|
+
f"{f', columns {start_column}-{end_column}' if start_column is not None or end_column is not None else ''}"
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
return result
|
|
226
|
+
|
|
227
|
+
except Exception as e:
|
|
228
|
+
log_error(f"Failed to read partial file {file_path}: {e}")
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def read_file_lines_range(
|
|
233
|
+
file_path: str, start_line: int, end_line: int | None = None
|
|
234
|
+
) -> str | None:
|
|
235
|
+
"""
|
|
236
|
+
指定した行番号範囲でファイルの一部を読み込み(列指定なし)
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
file_path: 読み込むファイルのパス
|
|
240
|
+
start_line: 開始行番号(1ベース)
|
|
241
|
+
end_line: 終了行番号(Noneの場合はファイル末尾まで、1ベース)
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
指定範囲のファイル内容(文字列)、エラーの場合はNone
|
|
245
|
+
"""
|
|
246
|
+
return read_file_partial(file_path, start_line, end_line)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Language-specific formatters
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Base formatter for language-specific formatting.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import csv
|
|
7
|
+
import io
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BaseFormatter(ABC):
|
|
13
|
+
"""Base class for language-specific formatters"""
|
|
14
|
+
|
|
15
|
+
@abstractmethod
|
|
16
|
+
def __init__(self) -> None:
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def format_summary(self, analysis_result: dict[str, Any]) -> str:
|
|
21
|
+
"""Format summary output"""
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def format_structure(self, analysis_result: dict[str, Any]) -> str:
|
|
26
|
+
"""Format structure analysis output"""
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def format_advanced(
|
|
31
|
+
self, analysis_result: dict[str, Any], output_format: str = "json"
|
|
32
|
+
) -> str:
|
|
33
|
+
"""Format advanced analysis output"""
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def format_table(
|
|
38
|
+
self, analysis_result: dict[str, Any], table_type: str = "full"
|
|
39
|
+
) -> str:
|
|
40
|
+
"""Format table output"""
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class BaseTableFormatter(ABC):
|
|
45
|
+
"""Base class for language-specific table formatters"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, format_type: str = "full"):
|
|
48
|
+
self.format_type = format_type
|
|
49
|
+
|
|
50
|
+
def _get_platform_newline(self) -> str:
|
|
51
|
+
"""Get platform-specific newline code"""
|
|
52
|
+
import os
|
|
53
|
+
|
|
54
|
+
return "\r\n" if os.name == "nt" else "\n" # Windows uses \r\n, others use \n
|
|
55
|
+
|
|
56
|
+
def _convert_to_platform_newlines(self, text: str) -> str:
|
|
57
|
+
"""Convert regular \n to platform-specific newline code"""
|
|
58
|
+
platform_newline = self._get_platform_newline()
|
|
59
|
+
if platform_newline != "\n":
|
|
60
|
+
return text.replace("\n", platform_newline)
|
|
61
|
+
return text
|
|
62
|
+
|
|
63
|
+
def format_structure(self, structure_data: dict[str, Any]) -> str:
|
|
64
|
+
"""Format structure data in table format"""
|
|
65
|
+
if self.format_type == "full":
|
|
66
|
+
result = self._format_full_table(structure_data)
|
|
67
|
+
elif self.format_type == "compact":
|
|
68
|
+
result = self._format_compact_table(structure_data)
|
|
69
|
+
elif self.format_type == "csv":
|
|
70
|
+
result = self._format_csv(structure_data)
|
|
71
|
+
else:
|
|
72
|
+
raise ValueError(f"Unsupported format type: {self.format_type}")
|
|
73
|
+
|
|
74
|
+
# Finally convert to platform-specific newline code
|
|
75
|
+
if self.format_type == "csv":
|
|
76
|
+
return result
|
|
77
|
+
|
|
78
|
+
return self._convert_to_platform_newlines(result)
|
|
79
|
+
|
|
80
|
+
@abstractmethod
|
|
81
|
+
def _format_full_table(self, data: dict[str, Any]) -> str:
|
|
82
|
+
"""Full table format (language-specific implementation)"""
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
@abstractmethod
|
|
86
|
+
def _format_compact_table(self, data: dict[str, Any]) -> str:
|
|
87
|
+
"""Compact table format (language-specific implementation)"""
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
def _format_csv(self, data: dict[str, Any]) -> str:
|
|
91
|
+
"""CSV format (common implementation)"""
|
|
92
|
+
output = io.StringIO()
|
|
93
|
+
writer = csv.writer(output, lineterminator="\n")
|
|
94
|
+
|
|
95
|
+
# Header
|
|
96
|
+
writer.writerow(
|
|
97
|
+
["Type", "Name", "Signature", "Visibility", "Lines", "Complexity", "Doc"]
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Fields
|
|
101
|
+
for field in data.get("fields", []):
|
|
102
|
+
writer.writerow(
|
|
103
|
+
[
|
|
104
|
+
"Field",
|
|
105
|
+
str(field.get("name", "")),
|
|
106
|
+
f"{str(field.get('name', ''))}:{str(field.get('type', ''))}",
|
|
107
|
+
str(field.get("visibility", "")),
|
|
108
|
+
f"{field.get('line_range', {}).get('start', 0)}-{field.get('line_range', {}).get('end', 0)}",
|
|
109
|
+
"",
|
|
110
|
+
self._clean_csv_text(
|
|
111
|
+
self._extract_doc_summary(str(field.get("javadoc", "")))
|
|
112
|
+
),
|
|
113
|
+
]
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Methods
|
|
117
|
+
for method in data.get("methods", []):
|
|
118
|
+
writer.writerow(
|
|
119
|
+
[
|
|
120
|
+
"Constructor" if method.get("is_constructor", False) else "Method",
|
|
121
|
+
str(method.get("name", "")),
|
|
122
|
+
self._clean_csv_text(self._create_full_signature(method)),
|
|
123
|
+
str(method.get("visibility", "")),
|
|
124
|
+
f"{method.get('line_range', {}).get('start', 0)}-{method.get('line_range', {}).get('end', 0)}",
|
|
125
|
+
method.get("complexity_score", 0),
|
|
126
|
+
self._clean_csv_text(
|
|
127
|
+
self._extract_doc_summary(str(method.get("javadoc", "")))
|
|
128
|
+
),
|
|
129
|
+
]
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
csv_content = output.getvalue()
|
|
133
|
+
csv_content = csv_content.replace("\r\n", "\n").replace("\r", "\n")
|
|
134
|
+
csv_content = csv_content.rstrip("\n")
|
|
135
|
+
output.close()
|
|
136
|
+
|
|
137
|
+
return csv_content
|
|
138
|
+
|
|
139
|
+
# Common helper methods
|
|
140
|
+
def _create_full_signature(self, method: dict[str, Any]) -> str:
|
|
141
|
+
"""Create complete method signature"""
|
|
142
|
+
params = method.get("parameters", [])
|
|
143
|
+
param_strs = []
|
|
144
|
+
for param in params:
|
|
145
|
+
if isinstance(param, dict):
|
|
146
|
+
param_type = str(param.get("type", "Object"))
|
|
147
|
+
param_name = str(param.get("name", "param"))
|
|
148
|
+
param_strs.append(f"{param_name}:{param_type}")
|
|
149
|
+
else:
|
|
150
|
+
param_strs.append(str(param))
|
|
151
|
+
|
|
152
|
+
params_str = ", ".join(param_strs)
|
|
153
|
+
return_type = str(method.get("return_type", "void"))
|
|
154
|
+
|
|
155
|
+
modifiers = []
|
|
156
|
+
if method.get("is_static", False):
|
|
157
|
+
modifiers.append("[static]")
|
|
158
|
+
|
|
159
|
+
modifier_str = " ".join(modifiers)
|
|
160
|
+
signature = f"({params_str}):{return_type}"
|
|
161
|
+
|
|
162
|
+
if modifier_str:
|
|
163
|
+
signature += f" {modifier_str}"
|
|
164
|
+
|
|
165
|
+
return signature
|
|
166
|
+
|
|
167
|
+
def _convert_visibility(self, visibility: str) -> str:
|
|
168
|
+
"""Convert visibility to symbol"""
|
|
169
|
+
mapping = {"public": "+", "private": "-", "protected": "#", "package": "~"}
|
|
170
|
+
return mapping.get(visibility, visibility)
|
|
171
|
+
|
|
172
|
+
def _extract_doc_summary(self, javadoc: str) -> str:
|
|
173
|
+
"""Extract summary from documentation"""
|
|
174
|
+
if not javadoc:
|
|
175
|
+
return "-"
|
|
176
|
+
|
|
177
|
+
# Remove comment symbols
|
|
178
|
+
clean_doc = (
|
|
179
|
+
javadoc.replace("/**", "").replace("*/", "").replace("*", "").strip()
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Get first line
|
|
183
|
+
lines = clean_doc.split("\n")
|
|
184
|
+
first_line = lines[0].strip()
|
|
185
|
+
|
|
186
|
+
# Truncate if too long
|
|
187
|
+
if len(first_line) > 50:
|
|
188
|
+
first_line = first_line[:47] + "..."
|
|
189
|
+
|
|
190
|
+
return first_line.replace("|", "\\|").replace("\n", " ")
|
|
191
|
+
|
|
192
|
+
def _clean_csv_text(self, text: str) -> str:
|
|
193
|
+
"""Text cleaning for CSV format"""
|
|
194
|
+
if not text:
|
|
195
|
+
return ""
|
|
196
|
+
|
|
197
|
+
cleaned = text.replace("\r\n", " ").replace("\r", " ").replace("\n", " ")
|
|
198
|
+
cleaned = " ".join(cleaned.split())
|
|
199
|
+
cleaned = cleaned.replace('"', '""')
|
|
200
|
+
|
|
201
|
+
return cleaned
|