tree-sitter-analyzer 0.1.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.

Potentially problematic release.


This version of tree-sitter-analyzer might be problematic. Click here for more details.

Files changed (78) hide show
  1. tree_sitter_analyzer/__init__.py +121 -0
  2. tree_sitter_analyzer/__main__.py +12 -0
  3. tree_sitter_analyzer/api.py +539 -0
  4. tree_sitter_analyzer/cli/__init__.py +39 -0
  5. tree_sitter_analyzer/cli/__main__.py +13 -0
  6. tree_sitter_analyzer/cli/commands/__init__.py +27 -0
  7. tree_sitter_analyzer/cli/commands/advanced_command.py +88 -0
  8. tree_sitter_analyzer/cli/commands/base_command.py +155 -0
  9. tree_sitter_analyzer/cli/commands/default_command.py +19 -0
  10. tree_sitter_analyzer/cli/commands/partial_read_command.py +133 -0
  11. tree_sitter_analyzer/cli/commands/query_command.py +82 -0
  12. tree_sitter_analyzer/cli/commands/structure_command.py +121 -0
  13. tree_sitter_analyzer/cli/commands/summary_command.py +93 -0
  14. tree_sitter_analyzer/cli/commands/table_command.py +233 -0
  15. tree_sitter_analyzer/cli/info_commands.py +121 -0
  16. tree_sitter_analyzer/cli_main.py +276 -0
  17. tree_sitter_analyzer/core/__init__.py +20 -0
  18. tree_sitter_analyzer/core/analysis_engine.py +574 -0
  19. tree_sitter_analyzer/core/cache_service.py +330 -0
  20. tree_sitter_analyzer/core/engine.py +560 -0
  21. tree_sitter_analyzer/core/parser.py +288 -0
  22. tree_sitter_analyzer/core/query.py +502 -0
  23. tree_sitter_analyzer/encoding_utils.py +460 -0
  24. tree_sitter_analyzer/exceptions.py +340 -0
  25. tree_sitter_analyzer/file_handler.py +222 -0
  26. tree_sitter_analyzer/formatters/__init__.py +1 -0
  27. tree_sitter_analyzer/formatters/base_formatter.py +168 -0
  28. tree_sitter_analyzer/formatters/formatter_factory.py +74 -0
  29. tree_sitter_analyzer/formatters/java_formatter.py +270 -0
  30. tree_sitter_analyzer/formatters/python_formatter.py +235 -0
  31. tree_sitter_analyzer/interfaces/__init__.py +10 -0
  32. tree_sitter_analyzer/interfaces/cli.py +557 -0
  33. tree_sitter_analyzer/interfaces/cli_adapter.py +319 -0
  34. tree_sitter_analyzer/interfaces/mcp_adapter.py +170 -0
  35. tree_sitter_analyzer/interfaces/mcp_server.py +416 -0
  36. tree_sitter_analyzer/java_analyzer.py +219 -0
  37. tree_sitter_analyzer/language_detector.py +400 -0
  38. tree_sitter_analyzer/language_loader.py +228 -0
  39. tree_sitter_analyzer/languages/__init__.py +11 -0
  40. tree_sitter_analyzer/languages/java_plugin.py +1113 -0
  41. tree_sitter_analyzer/languages/python_plugin.py +712 -0
  42. tree_sitter_analyzer/mcp/__init__.py +32 -0
  43. tree_sitter_analyzer/mcp/resources/__init__.py +47 -0
  44. tree_sitter_analyzer/mcp/resources/code_file_resource.py +213 -0
  45. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +550 -0
  46. tree_sitter_analyzer/mcp/server.py +319 -0
  47. tree_sitter_analyzer/mcp/tools/__init__.py +36 -0
  48. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +558 -0
  49. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +245 -0
  50. tree_sitter_analyzer/mcp/tools/base_tool.py +55 -0
  51. tree_sitter_analyzer/mcp/tools/get_positions_tool.py +448 -0
  52. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +302 -0
  53. tree_sitter_analyzer/mcp/tools/table_format_tool.py +359 -0
  54. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +476 -0
  55. tree_sitter_analyzer/mcp/utils/__init__.py +106 -0
  56. tree_sitter_analyzer/mcp/utils/error_handler.py +549 -0
  57. tree_sitter_analyzer/models.py +481 -0
  58. tree_sitter_analyzer/output_manager.py +264 -0
  59. tree_sitter_analyzer/plugins/__init__.py +334 -0
  60. tree_sitter_analyzer/plugins/base.py +446 -0
  61. tree_sitter_analyzer/plugins/java_plugin.py +625 -0
  62. tree_sitter_analyzer/plugins/javascript_plugin.py +439 -0
  63. tree_sitter_analyzer/plugins/manager.py +355 -0
  64. tree_sitter_analyzer/plugins/plugin_loader.py +83 -0
  65. tree_sitter_analyzer/plugins/python_plugin.py +598 -0
  66. tree_sitter_analyzer/plugins/registry.py +366 -0
  67. tree_sitter_analyzer/queries/__init__.py +27 -0
  68. tree_sitter_analyzer/queries/java.py +394 -0
  69. tree_sitter_analyzer/queries/javascript.py +149 -0
  70. tree_sitter_analyzer/queries/python.py +286 -0
  71. tree_sitter_analyzer/queries/typescript.py +230 -0
  72. tree_sitter_analyzer/query_loader.py +260 -0
  73. tree_sitter_analyzer/table_formatter.py +448 -0
  74. tree_sitter_analyzer/utils.py +201 -0
  75. tree_sitter_analyzer-0.1.0.dist-info/METADATA +581 -0
  76. tree_sitter_analyzer-0.1.0.dist-info/RECORD +78 -0
  77. tree_sitter_analyzer-0.1.0.dist-info/WHEEL +4 -0
  78. tree_sitter_analyzer-0.1.0.dist-info/entry_points.txt +8 -0
@@ -0,0 +1,448 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Table Formatter for Tree-sitter Analyzer
5
+
6
+ Provides table-formatted output for Java code analysis results.
7
+ """
8
+
9
+ import csv
10
+ import io
11
+ import os
12
+ from typing import Any, Dict, List, Optional
13
+
14
+ from .models import AnalysisResult
15
+
16
+
17
+ class TableFormatter:
18
+ """テーブル形式でのフォーマッター"""
19
+
20
+ def __init__(self, format_type: str = "full", language: str = "java", include_javadoc: bool = False):
21
+ self.format_type = format_type
22
+ self.language = language
23
+ self.include_javadoc = include_javadoc
24
+
25
+ def _get_platform_newline(self) -> str:
26
+ """プラットフォーム固有の改行コードを取得"""
27
+ return os.linesep
28
+
29
+ def _convert_to_platform_newlines(self, text: str) -> str:
30
+ """通常の\nをプラットフォーム固有の改行コードに変換"""
31
+ if os.linesep != "\n":
32
+ return text.replace("\n", os.linesep)
33
+ return text
34
+
35
+ def format_structure(self, structure_data: Dict[str, Any]) -> str:
36
+ """構造データをテーブル形式でフォーマット"""
37
+ if self.format_type == "full":
38
+ result = self._format_full_table(structure_data)
39
+ elif self.format_type == "compact":
40
+ result = self._format_compact_table(structure_data)
41
+ elif self.format_type == "csv":
42
+ result = self._format_csv(structure_data)
43
+ else:
44
+ raise ValueError(f"Unsupported format type: {self.format_type}")
45
+
46
+ # 最終的にプラットフォーム固有の改行コードに変換
47
+ # CSV形式の場合は改行変換をスキップ(改行制御は_format_csv内で完結)
48
+ if self.format_type == "csv":
49
+ return result
50
+
51
+ return self._convert_to_platform_newlines(result)
52
+
53
+ def _format_full_table(self, data: Dict[str, Any]) -> str:
54
+ """完全版テーブル形式"""
55
+ lines = []
56
+
57
+ # ヘッダー - 複数クラスがある場合はファイル名を使用
58
+ classes = data.get("classes", [])
59
+ if classes is None:
60
+ classes = []
61
+ if len(classes) > 1:
62
+ # 複数クラスがある場合はファイル名を使用
63
+ file_name = data.get("file_path", "Unknown").split("/")[-1].split("\\")[-1]
64
+ lines.append(f"# {file_name}")
65
+ else:
66
+ # 単一クラスの場合は従来通り
67
+ class_name = classes[0].get("name", "Unknown") if classes else "Unknown"
68
+ lines.append(
69
+ f"# {(data.get('package') or {}).get('name', 'unknown')}.{class_name}"
70
+ )
71
+ lines.append("")
72
+
73
+ # Imports
74
+ imports = data.get("imports", [])
75
+ if imports:
76
+ lines.append("## Imports")
77
+ lines.append(f"```{self.language}")
78
+ for imp in imports:
79
+ lines.append(str(imp.get("statement", "")))
80
+ lines.append("```")
81
+ lines.append("")
82
+
83
+ # Class Info - 複数クラスに対応
84
+ classes = data.get("classes", [])
85
+ if classes is None:
86
+ classes = []
87
+ if len(classes) > 1:
88
+ lines.append("## Classes")
89
+ lines.append("| Class | Type | Visibility | Lines | Methods | Fields |")
90
+ lines.append("|-------|------|------------|-------|---------|--------|")
91
+
92
+ for class_info in classes:
93
+ name = str(class_info.get("name", "Unknown"))
94
+ class_type = str(class_info.get("type", "class"))
95
+ visibility = str(class_info.get("visibility", "public"))
96
+ line_range = class_info.get("line_range", {})
97
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
98
+
99
+ # このクラスのメソッド数とフィールド数を計算
100
+ class_methods = [m for m in data.get("methods", [])
101
+ if line_range.get('start', 0) <= m.get('line_range', {}).get('start', 0) <= line_range.get('end', 0)]
102
+ class_fields = [f for f in data.get("fields", [])
103
+ if line_range.get('start', 0) <= f.get('line_range', {}).get('start', 0) <= line_range.get('end', 0)]
104
+
105
+ lines.append(f"| {name} | {class_type} | {visibility} | {lines_str} | {len(class_methods)} | {len(class_fields)} |")
106
+ else:
107
+ # 単一クラスの場合は従来通り
108
+ lines.append("## Class Info")
109
+ lines.append("| Property | Value |")
110
+ lines.append("|----------|-------|")
111
+
112
+ package_name = (data.get("package") or {}).get("name", "unknown")
113
+ class_info = data.get("classes", [{}])[0] if data.get("classes") else {}
114
+ stats = data.get("statistics") or {}
115
+
116
+ lines.append(f"| Package | {package_name} |")
117
+ lines.append(f"| Type | {str(class_info.get('type', 'class'))} |")
118
+ lines.append(f"| Visibility | {str(class_info.get('visibility', 'public'))} |")
119
+ lines.append(
120
+ f"| Lines | {class_info.get('line_range', {}).get('start', 0)}-{class_info.get('line_range', {}).get('end', 0)} |"
121
+ )
122
+ lines.append(f"| Total Methods | {stats.get('method_count', 0)} |")
123
+ lines.append(f"| Total Fields | {stats.get('field_count', 0)} |")
124
+
125
+ lines.append("")
126
+
127
+ # Fields
128
+ fields = data.get("fields", [])
129
+ if fields is None:
130
+ fields = []
131
+ if fields:
132
+ lines.append("## Fields")
133
+ lines.append("| Name | Type | Vis | Modifiers | Line | Doc |")
134
+ lines.append("|------|------|-----|-----------|------|-----|")
135
+
136
+ for field in fields:
137
+ name = str(field.get("name", ""))
138
+ field_type = str(field.get("type", ""))
139
+ visibility = self._convert_visibility(str(field.get("visibility", "")))
140
+ modifiers = ",".join([str(m) for m in field.get("modifiers", [])])
141
+ line = field.get("line_range", {}).get("start", 0)
142
+ if self.include_javadoc:
143
+ doc = self._extract_doc_summary(str(field.get("javadoc", "")))
144
+ else:
145
+ doc = "-"
146
+
147
+ lines.append(
148
+ f"| {name} | {field_type} | {visibility} | {modifiers} | {line} | {doc} |"
149
+ )
150
+ lines.append("")
151
+
152
+ # Constructor
153
+ constructors = [
154
+ m for m in (data.get("methods") or []) if m.get("is_constructor", False)
155
+ ]
156
+ if constructors:
157
+ lines.append("## Constructor")
158
+ lines.append("| Method | Signature | Vis | Lines | Cols | Cx | Doc |")
159
+ lines.append("|--------|-----------|-----|-------|------|----|----|")
160
+
161
+ for method in constructors:
162
+ lines.append(self._format_method_row(method))
163
+ lines.append("")
164
+
165
+ # Public Methods
166
+ public_methods = [
167
+ m
168
+ for m in (data.get("methods") or [])
169
+ if not m.get("is_constructor", False)
170
+ and str(m.get("visibility")) == "public"
171
+ ]
172
+ if public_methods:
173
+ lines.append("## Public Methods")
174
+ lines.append("| Method | Signature | Vis | Lines | Cols | Cx | Doc |")
175
+ lines.append("|--------|-----------|-----|-------|------|----|----|")
176
+
177
+ for method in public_methods:
178
+ lines.append(self._format_method_row(method))
179
+ lines.append("")
180
+
181
+ # Private Methods
182
+ private_methods = [
183
+ m
184
+ for m in (data.get("methods") or [])
185
+ if not m.get("is_constructor", False)
186
+ and str(m.get("visibility")) == "private"
187
+ ]
188
+ if private_methods:
189
+ lines.append("## Private Methods")
190
+ lines.append("| Method | Signature | Vis | Lines | Cols | Cx | Doc |")
191
+ lines.append("|--------|-----------|-----|-------|------|----|----|")
192
+
193
+ for method in private_methods:
194
+ lines.append(self._format_method_row(method))
195
+ lines.append("")
196
+
197
+ # 末尾の空行を削除
198
+ while lines and lines[-1] == "":
199
+ lines.pop()
200
+
201
+ return "\n".join(lines)
202
+
203
+ def _format_compact_table(self, data: Dict[str, Any]) -> str:
204
+ """コンパクト版テーブル形式"""
205
+ lines = []
206
+
207
+ # ヘッダー
208
+ package_name = (data.get("package") or {}).get("name", "unknown")
209
+ classes = data.get("classes", [])
210
+ if classes is None:
211
+ classes = []
212
+ class_name = classes[0].get("name", "Unknown") if classes else "Unknown"
213
+ lines.append(f"# {package_name}.{class_name}")
214
+ lines.append("")
215
+
216
+ # 基本情報
217
+ stats = data.get("statistics") or {}
218
+ lines.append("## Info")
219
+ lines.append("| Property | Value |")
220
+ lines.append("|----------|-------|")
221
+ lines.append(f"| Package | {package_name} |")
222
+ lines.append(f"| Methods | {stats.get('method_count', 0)} |")
223
+ lines.append(f"| Fields | {stats.get('field_count', 0)} |")
224
+ lines.append("")
225
+
226
+ # メソッド(簡略版)
227
+ methods = data.get("methods", [])
228
+ if methods is None:
229
+ methods = []
230
+ if methods:
231
+ lines.append("## Methods")
232
+ lines.append("| Method | Sig | V | L | Cx | Doc |")
233
+ lines.append("|--------|-----|---|---|----|----|")
234
+
235
+ for method in methods:
236
+ name = str(method.get("name", ""))
237
+ signature = self._create_compact_signature(method)
238
+ visibility = self._convert_visibility(str(method.get("visibility", "")))
239
+ line_range = method.get("line_range", {})
240
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
241
+ complexity = method.get("complexity_score", 0)
242
+ doc = self._clean_csv_text(
243
+ self._extract_doc_summary(str(method.get("javadoc", "")))
244
+ )
245
+
246
+ lines.append(
247
+ f"| {name} | {signature} | {visibility} | {lines_str} | {complexity} | {doc} |"
248
+ )
249
+ lines.append("")
250
+
251
+ # 末尾の空行を削除
252
+ while lines and lines[-1] == "":
253
+ lines.pop()
254
+
255
+ return "\n".join(lines)
256
+
257
+ def _format_csv(self, data: Dict[str, Any]) -> str:
258
+ """CSV形式"""
259
+ output = io.StringIO()
260
+ writer = csv.writer(output, lineterminator="\n") # 改行文字を明示的に指定
261
+
262
+ # ヘッダー
263
+ writer.writerow(
264
+ ["Type", "Name", "Signature", "Visibility", "Lines", "Complexity", "Doc"]
265
+ )
266
+
267
+ # フィールド
268
+ for field in data.get("fields", []):
269
+ writer.writerow(
270
+ [
271
+ "Field",
272
+ str(field.get("name", "")),
273
+ f"{str(field.get('name', ''))}:{str(field.get('type', ''))}",
274
+ str(field.get("visibility", "")),
275
+ f"{field.get('line_range', {}).get('start', 0)}-{field.get('line_range', {}).get('end', 0)}",
276
+ "",
277
+ self._clean_csv_text(
278
+ self._extract_doc_summary(str(field.get("javadoc", "")))
279
+ ),
280
+ ]
281
+ )
282
+
283
+ # メソッド
284
+ for method in data.get("methods", []):
285
+ writer.writerow(
286
+ [
287
+ "Constructor" if method.get("is_constructor", False) else "Method",
288
+ str(method.get("name", "")),
289
+ self._clean_csv_text(self._create_full_signature(method)),
290
+ str(method.get("visibility", "")),
291
+ f"{method.get('line_range', {}).get('start', 0)}-{method.get('line_range', {}).get('end', 0)}",
292
+ method.get("complexity_score", 0),
293
+ self._clean_csv_text(
294
+ self._extract_doc_summary(str(method.get("javadoc", "")))
295
+ ),
296
+ ]
297
+ )
298
+
299
+ # CSV出力の改行を完全に制御
300
+ csv_content = output.getvalue()
301
+ # 全ての改行パターンを統一し、末尾の改行を除去
302
+ csv_content = csv_content.replace("\r\n", "\n").replace("\r", "\n")
303
+ csv_content = csv_content.rstrip("\n")
304
+ output.close()
305
+
306
+ return csv_content
307
+
308
+ def _format_method_row(self, method: Dict[str, Any]) -> str:
309
+ """メソッド行のフォーマット"""
310
+ name = str(method.get("name", ""))
311
+ signature = self._create_full_signature(method)
312
+ visibility = self._convert_visibility(str(method.get("visibility", "")))
313
+ line_range = method.get("line_range", {})
314
+ lines_str = f"{line_range.get('start', 0)}-{line_range.get('end', 0)}"
315
+ cols_str = "5-6" # デフォルト値(実際の実装では正確な値を取得)
316
+ complexity = method.get("complexity_score", 0)
317
+ if self.include_javadoc:
318
+ doc = self._clean_csv_text(
319
+ self._extract_doc_summary(str(method.get("javadoc", "")))
320
+ )
321
+ else:
322
+ doc = "-"
323
+
324
+ return f"| {name} | {signature} | {visibility} | {lines_str} | {cols_str} | {complexity} | {doc} |"
325
+
326
+ def _create_full_signature(self, method: Dict[str, Any]) -> str:
327
+ """完全なメソッドシグネチャを作成"""
328
+ params = method.get("parameters", [])
329
+ param_strs = []
330
+ for param in params:
331
+ param_type = str(param.get("type", "Object"))
332
+ param_name = str(param.get("name", "param"))
333
+ param_strs.append(f"{param_name}:{param_type}")
334
+
335
+ params_str = ", ".join(param_strs)
336
+ return_type = str(method.get("return_type", "void"))
337
+
338
+ modifiers = []
339
+ if method.get("is_static", False):
340
+ modifiers.append("[static]")
341
+
342
+ modifier_str = " ".join(modifiers)
343
+ signature = f"({params_str}):{return_type}"
344
+
345
+ if modifier_str:
346
+ signature += f" {modifier_str}"
347
+
348
+ return signature
349
+
350
+ def _create_compact_signature(self, method: Dict[str, Any]) -> str:
351
+ """コンパクトなメソッドシグネチャを作成"""
352
+ params = method.get("parameters", [])
353
+ param_types = [self._shorten_type(p.get("type", "O")) for p in params]
354
+ params_str = ",".join(param_types)
355
+ return_type = self._shorten_type(method.get("return_type", "void"))
356
+
357
+ return f"({params_str}):{return_type}"
358
+
359
+ def _shorten_type(self, type_name: Any) -> str:
360
+ """型名を短縮"""
361
+ if type_name is None:
362
+ return "O"
363
+
364
+ # Convert non-string types to string
365
+ if not isinstance(type_name, str):
366
+ type_name = str(type_name)
367
+
368
+ type_mapping = {
369
+ "String": "S",
370
+ "int": "i",
371
+ "long": "l",
372
+ "double": "d",
373
+ "boolean": "b",
374
+ "void": "void",
375
+ "Object": "O",
376
+ "Exception": "E",
377
+ "SQLException": "SE",
378
+ "IllegalArgumentException": "IAE",
379
+ "RuntimeException": "RE",
380
+ }
381
+
382
+ # Map<String,Object> -> M<S,O>
383
+ if "Map<" in type_name:
384
+ return (
385
+ type_name.replace("Map<", "M<")
386
+ .replace("String", "S")
387
+ .replace("Object", "O")
388
+ )
389
+
390
+ # List<String> -> L<S>
391
+ if "List<" in type_name:
392
+ return type_name.replace("List<", "L<").replace("String", "S")
393
+
394
+ # String[] -> S[]
395
+ if "[]" in type_name:
396
+ base_type = type_name.replace("[]", "")
397
+ if base_type:
398
+ return type_mapping.get(base_type, base_type[0].upper()) + "[]"
399
+ else:
400
+ return "O[]"
401
+
402
+ return type_mapping.get(type_name, type_name)
403
+
404
+ def _convert_visibility(self, visibility: str) -> str:
405
+ """可視性を記号に変換"""
406
+ mapping = {"public": "+", "private": "-", "protected": "#", "package": "~"}
407
+ return mapping.get(visibility, visibility)
408
+
409
+ def _extract_doc_summary(self, javadoc: str) -> str:
410
+ """JavaDocから要約を抽出"""
411
+ if not javadoc:
412
+ return "-"
413
+
414
+ # コメント記号を除去
415
+ clean_doc = (
416
+ javadoc.replace("/**", "").replace("*/", "").replace("*", "").strip()
417
+ )
418
+
419
+ # 最初の行を取得(通常の\nのみを使用)
420
+ lines = clean_doc.split("\n")
421
+ first_line = lines[0].strip()
422
+
423
+ # 長すぎる場合は切り詰め
424
+ if len(first_line) > 50:
425
+ first_line = first_line[:47] + "..."
426
+
427
+ # Markdownテーブルで問題となる文字をエスケープ(通常の\nのみを使用)
428
+ return first_line.replace("|", "\\|").replace("\n", " ")
429
+
430
+ def _clean_csv_text(self, text: str) -> str:
431
+ """CSV形式用のテキストクリーニング"""
432
+ if not text:
433
+ return ""
434
+
435
+ # 改行文字を全て空白に置換
436
+ cleaned = text.replace("\r\n", " ").replace("\r", " ").replace("\n", " ")
437
+ # 連続する空白を単一の空白に変換
438
+ cleaned = " ".join(cleaned.split())
439
+ # CSVで問題となる文字をエスケープ
440
+ cleaned = cleaned.replace('"', '""') # ダブルクォートをエスケープ
441
+
442
+ return cleaned
443
+
444
+
445
+ def create_table_formatter(format_type: str, language: str = "java", include_javadoc: bool = False):
446
+ """テーブルフォーマッターを作成(新しいファクトリーを使用)"""
447
+ # 直接TableFormatterを作成(JavaDoc対応のため)
448
+ return TableFormatter(format_type, language, include_javadoc)
@@ -0,0 +1,201 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Utilities for Tree-sitter Analyzer
5
+
6
+ Provides logging, debugging, and common utility functions.
7
+ """
8
+
9
+ import logging
10
+ import sys
11
+ from functools import wraps
12
+ from typing import Any, Optional
13
+
14
+
15
+ # Configure global logger
16
+ def setup_logger(
17
+ name: str = "tree_sitter_analyzer", level: int = logging.INFO
18
+ ) -> logging.Logger:
19
+ """Setup unified logger for the project"""
20
+ import os
21
+
22
+ # 環境変数からログレベルを取得
23
+ env_level = os.environ.get('LOG_LEVEL', '').upper()
24
+ if env_level == 'DEBUG':
25
+ level = logging.DEBUG
26
+ elif env_level == 'INFO':
27
+ level = logging.INFO
28
+ elif env_level == 'WARNING':
29
+ level = logging.WARNING
30
+ elif env_level == 'ERROR':
31
+ level = logging.ERROR
32
+
33
+ logger = logging.getLogger(name)
34
+
35
+ if not logger.handlers: # Avoid duplicate handlers
36
+ handler = logging.StreamHandler(sys.stdout)
37
+ formatter = logging.Formatter(
38
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
39
+ )
40
+ handler.setFormatter(formatter)
41
+ logger.addHandler(handler)
42
+ logger.setLevel(level)
43
+
44
+ return logger
45
+
46
+
47
+ # Global logger instance
48
+ logger = setup_logger()
49
+
50
+
51
+ def log_info(message: str, *args: Any, **kwargs: Any) -> None:
52
+ """Log info message"""
53
+ logger.info(message, *args, **kwargs)
54
+
55
+
56
+ def log_warning(message: str, *args: Any, **kwargs: Any) -> None:
57
+ """Log warning message"""
58
+ logger.warning(message, *args, **kwargs)
59
+
60
+
61
+ def log_error(message: str, *args: Any, **kwargs: Any) -> None:
62
+ """Log error message"""
63
+ logger.error(message, *args, **kwargs)
64
+
65
+
66
+ def log_debug(message: str, *args: Any, **kwargs: Any) -> None:
67
+ """Log debug message"""
68
+ logger.debug(message, *args, **kwargs)
69
+
70
+
71
+ def suppress_output(func: Any) -> Any:
72
+ """Decorator to suppress print statements in production"""
73
+
74
+ @wraps(func)
75
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
76
+ # Check if we're in test/debug mode
77
+ if getattr(sys, "_testing", False):
78
+ return func(*args, **kwargs)
79
+
80
+ # Redirect stdout to suppress prints
81
+ old_stdout = sys.stdout
82
+ sys.stdout = (
83
+ open("/dev/null", "w") if sys.platform != "win32" else open("nul", "w")
84
+ )
85
+
86
+ try:
87
+ result = func(*args, **kwargs)
88
+ finally:
89
+ sys.stdout.close()
90
+ sys.stdout = old_stdout
91
+
92
+ return result
93
+
94
+ return wrapper
95
+
96
+
97
+ class QuietMode:
98
+ """Context manager for quiet execution"""
99
+
100
+ def __init__(self, enabled: bool = True):
101
+ self.enabled = enabled
102
+ self.old_level: Optional[int] = None
103
+
104
+ def __enter__(self) -> "QuietMode":
105
+ if self.enabled:
106
+ self.old_level = logger.level
107
+ logger.setLevel(logging.ERROR)
108
+ return self # type: ignore
109
+
110
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
111
+ if self.enabled and self.old_level is not None:
112
+ logger.setLevel(self.old_level)
113
+
114
+
115
+ def safe_print(message: str, level: str = "info", quiet: bool = False) -> None:
116
+ """Safe print function that can be controlled"""
117
+ if quiet:
118
+ return
119
+
120
+ level_map = {
121
+ "info": log_info,
122
+ "warning": log_warning,
123
+ "error": log_error,
124
+ "debug": log_debug,
125
+ }
126
+
127
+ log_func = level_map.get(level.lower(), log_info)
128
+ log_func(message)
129
+
130
+
131
+ def create_performance_logger(name: str) -> logging.Logger:
132
+ """Create performance-focused logger"""
133
+ perf_logger = logging.getLogger(f"{name}.performance")
134
+
135
+ if not perf_logger.handlers:
136
+ handler = logging.StreamHandler()
137
+ formatter = logging.Formatter("%(asctime)s - PERF - %(message)s")
138
+ handler.setFormatter(formatter)
139
+ perf_logger.addHandler(handler)
140
+ perf_logger.setLevel(logging.INFO)
141
+
142
+ return perf_logger
143
+
144
+
145
+ # Performance logger instance
146
+ perf_logger = create_performance_logger("tree_sitter_analyzer")
147
+
148
+
149
+ def log_performance(
150
+ operation: str,
151
+ execution_time: Optional[float] = None,
152
+ details: Optional[dict] = None,
153
+ ) -> None:
154
+ """Log performance metrics"""
155
+ message = f"{operation}"
156
+ if execution_time is not None:
157
+ message += f": {execution_time:.4f}s"
158
+ if details:
159
+ if isinstance(details, dict):
160
+ detail_str = ", ".join([f"{k}: {v}" for k, v in details.items()])
161
+ else:
162
+ detail_str = str(details)
163
+ message += f" - {detail_str}"
164
+ perf_logger.info(message)
165
+
166
+
167
+ def setup_performance_logger() -> logging.Logger:
168
+ """Set up performance logging"""
169
+ perf_logger = logging.getLogger("performance")
170
+
171
+ # Add handler if not already configured
172
+ if not perf_logger.handlers:
173
+ handler = logging.StreamHandler()
174
+ formatter = logging.Formatter("%(asctime)s - Performance - %(message)s")
175
+ handler.setFormatter(formatter)
176
+ perf_logger.addHandler(handler)
177
+ perf_logger.setLevel(logging.INFO)
178
+
179
+ return perf_logger
180
+
181
+
182
+ class LoggingContext:
183
+ """Context manager for controlling logging behavior"""
184
+
185
+ def __init__(self, enabled: bool = True, level: Optional[int] = None):
186
+ self.enabled = enabled
187
+ self.level = level
188
+ self.old_level: Optional[int] = None
189
+ self.target_logger = (
190
+ logging.getLogger()
191
+ ) # Use root logger for compatibility with tests
192
+
193
+ def __enter__(self) -> "LoggingContext":
194
+ if self.enabled and self.level is not None:
195
+ self.old_level = self.target_logger.level
196
+ self.target_logger.setLevel(self.level)
197
+ return self # type: ignore
198
+
199
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
200
+ if self.enabled and self.old_level is not None:
201
+ self.target_logger.setLevel(self.old_level)