tree-sitter-analyzer 0.9.8__py3-none-any.whl → 1.0.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.

@@ -1,336 +1,335 @@
1
- #!/usr/bin/env python3
2
- """
3
- Utilities for Tree-sitter Analyzer
4
-
5
- Provides logging, debugging, and common utility functions.
6
- """
7
-
8
- import atexit
9
- import logging
10
- import sys
11
- from functools import wraps
12
- from typing import Any
13
-
14
-
15
- # Configure global logger
16
- def setup_logger(
17
- name: str = "tree_sitter_analyzer", level: int = logging.WARNING
18
- ) -> logging.Logger:
19
- """Setup unified logger for the project"""
20
- import os
21
-
22
- # Get log level from environment variable
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
- # Create a safe handler that writes to stderr to avoid breaking MCP stdio
37
- handler = SafeStreamHandler()
38
- formatter = logging.Formatter(
39
- "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
40
- )
41
- handler.setFormatter(formatter)
42
- logger.addHandler(handler)
43
-
44
- # Also log to a local file for debugging when launched by clients (e.g., Cursor)
45
- # This helps diagnose cases where stdio is captured by the client and logs are hidden.
46
- try:
47
- file_handler = logging.FileHandler(
48
- "cursor_mcp_server.log", encoding="utf-8"
49
- )
50
- file_handler.setFormatter(formatter)
51
- logger.addHandler(file_handler)
52
- except Exception as e:
53
- # Never let logging configuration break runtime behavior; log to stderr if possible
54
- if hasattr(sys, "stderr") and hasattr(sys.stderr, "write"):
55
- try:
56
- sys.stderr.write(
57
- f"[logging_setup] file handler init skipped: {e}\n"
58
- )
59
- except Exception:
60
- ...
61
-
62
- logger.setLevel(level)
63
-
64
- return logger
65
-
66
-
67
- class SafeStreamHandler(logging.StreamHandler):
68
- """
69
- A StreamHandler that safely handles closed streams
70
- """
71
-
72
- def __init__(self, stream=None):
73
- # Default to sys.stderr to keep stdout clean for MCP stdio
74
- super().__init__(stream if stream is not None else sys.stderr)
75
-
76
- def emit(self, record: Any) -> None:
77
- """
78
- Emit a record, safely handling closed streams
79
- """
80
- try:
81
- # Check if stream is closed before writing
82
- if hasattr(self.stream, "closed") and self.stream.closed:
83
- return
84
-
85
- # Check if we can write to the stream
86
- if not hasattr(self.stream, "write"):
87
- return
88
-
89
- super().emit(record)
90
- except (ValueError, OSError, AttributeError):
91
- # Silently ignore I/O errors during shutdown
92
- pass
93
- except Exception:
94
- # For any other unexpected errors, use handleError
95
- self.handleError(record)
96
-
97
-
98
- def setup_safe_logging_shutdown() -> None:
99
- """
100
- Setup safe logging shutdown to prevent I/O errors
101
- """
102
-
103
- def cleanup_logging() -> None:
104
- """Clean up logging handlers safely"""
105
- try:
106
- # Get all loggers
107
- loggers = [logging.getLogger()] + [
108
- logging.getLogger(name) for name in logging.Logger.manager.loggerDict
109
- ]
110
-
111
- for logger in loggers:
112
- for handler in logger.handlers[:]:
113
- try:
114
- handler.close()
115
- logger.removeHandler(handler)
116
- except Exception as e:
117
- if hasattr(sys, "stderr") and hasattr(sys.stderr, "write"):
118
- try:
119
- sys.stderr.write(
120
- f"[logging_cleanup] handler close/remove skipped: {e}\n"
121
- )
122
- except Exception:
123
- ...
124
- except Exception as e:
125
- if hasattr(sys, "stderr") and hasattr(sys.stderr, "write"):
126
- try:
127
- sys.stderr.write(f"[logging_cleanup] cleanup skipped: {e}\n")
128
- except Exception:
129
- ...
130
-
131
- # Register cleanup function
132
- atexit.register(cleanup_logging)
133
-
134
-
135
- # Setup safe shutdown on import
136
- setup_safe_logging_shutdown()
137
-
138
-
139
- # Global logger instance
140
- logger = setup_logger()
141
-
142
-
143
- def log_info(message: str, *args: Any, **kwargs: Any) -> None:
144
- """Log info message"""
145
- try:
146
- logger.info(message, *args, **kwargs)
147
- except (ValueError, OSError) as e:
148
- if hasattr(sys, "stderr") and hasattr(sys.stderr, "write"):
149
- try:
150
- sys.stderr.write(f"[log_info] suppressed: {e}\n")
151
- except Exception:
152
- ...
153
-
154
-
155
- def log_warning(message: str, *args: Any, **kwargs: Any) -> None:
156
- """Log warning message"""
157
- try:
158
- logger.warning(message, *args, **kwargs)
159
- except (ValueError, OSError) as e:
160
- if hasattr(sys, "stderr") and hasattr(sys.stderr, "write"):
161
- try:
162
- sys.stderr.write(f"[log_warning] suppressed: {e}\n")
163
- except Exception:
164
- ...
165
-
166
-
167
- def log_error(message: str, *args: Any, **kwargs: Any) -> None:
168
- """Log error message"""
169
- try:
170
- logger.error(message, *args, **kwargs)
171
- except (ValueError, OSError) as e:
172
- if hasattr(sys, "stderr") and hasattr(sys.stderr, "write"):
173
- try:
174
- sys.stderr.write(f"[log_error] suppressed: {e}\n")
175
- except Exception:
176
- ...
177
-
178
-
179
- def log_debug(message: str, *args: Any, **kwargs: Any) -> None:
180
- """Log debug message"""
181
- try:
182
- logger.debug(message, *args, **kwargs)
183
- except (ValueError, OSError) as e:
184
- if hasattr(sys, "stderr") and hasattr(sys.stderr, "write"):
185
- try:
186
- sys.stderr.write(f"[log_debug] suppressed: {e}\n")
187
- except Exception:
188
- ...
189
-
190
-
191
- def suppress_output(func: Any) -> Any:
192
- """Decorator to suppress print statements in production"""
193
-
194
- @wraps(func)
195
- def wrapper(*args: Any, **kwargs: Any) -> Any:
196
- # Check if we're in test/debug mode
197
- if getattr(sys, "_testing", False):
198
- return func(*args, **kwargs)
199
-
200
- # Redirect stdout to suppress prints
201
- old_stdout = sys.stdout
202
- try:
203
- sys.stdout = (
204
- open("/dev/null", "w") if sys.platform != "win32" else open("nul", "w")
205
- )
206
- result = func(*args, **kwargs)
207
- finally:
208
- try:
209
- sys.stdout.close()
210
- except Exception as e:
211
- if hasattr(sys, "stderr") and hasattr(sys.stderr, "write"):
212
- try:
213
- sys.stderr.write(
214
- f"[suppress_output] stdout close suppressed: {e}\n"
215
- )
216
- except Exception:
217
- ...
218
- sys.stdout = old_stdout
219
-
220
- return result
221
-
222
- return wrapper
223
-
224
-
225
- class QuietMode:
226
- """Context manager for quiet execution"""
227
-
228
- def __init__(self, enabled: bool = True):
229
- self.enabled = enabled
230
- self.old_level: int | None = None
231
-
232
- def __enter__(self) -> "QuietMode":
233
- if self.enabled:
234
- self.old_level = logger.level
235
- logger.setLevel(logging.ERROR)
236
- return self
237
-
238
- def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
239
- if self.enabled and self.old_level is not None:
240
- logger.setLevel(self.old_level)
241
-
242
-
243
- def safe_print(message: str, level: str = "info", quiet: bool = False) -> None:
244
- """Safe print function that can be controlled"""
245
- if quiet:
246
- return
247
-
248
- level_map = {
249
- "info": log_info,
250
- "warning": log_warning,
251
- "error": log_error,
252
- "debug": log_debug,
253
- }
254
-
255
- log_func = level_map.get(level.lower(), log_info)
256
- log_func(message)
257
-
258
-
259
- def create_performance_logger(name: str) -> logging.Logger:
260
- """Create performance-focused logger"""
261
- perf_logger = logging.getLogger(f"{name}.performance")
262
-
263
- if not perf_logger.handlers:
264
- handler = SafeStreamHandler()
265
- formatter = logging.Formatter("%(asctime)s - PERF - %(message)s")
266
- handler.setFormatter(formatter)
267
- perf_logger.addHandler(handler)
268
- perf_logger.setLevel(logging.DEBUG) # Change to DEBUG level
269
-
270
- return perf_logger
271
-
272
-
273
- # Performance logger instance
274
- perf_logger = create_performance_logger("tree_sitter_analyzer")
275
-
276
-
277
- def log_performance(
278
- operation: str,
279
- execution_time: float | None = None,
280
- details: dict[Any, Any] | str | None = None,
281
- ) -> None:
282
- """Log performance metrics"""
283
- try:
284
- message = f"{operation}"
285
- if execution_time is not None:
286
- message += f": {execution_time:.4f}s"
287
- if details:
288
- if isinstance(details, dict):
289
- detail_str = ", ".join([f"{k}: {v}" for k, v in details.items()])
290
- else:
291
- detail_str = str(details)
292
- message += f" - {detail_str}"
293
- perf_logger.debug(message) # Change to DEBUG level
294
- except (ValueError, OSError) as e:
295
- if hasattr(sys, "stderr") and hasattr(sys.stderr, "write"):
296
- try:
297
- sys.stderr.write(f"[log_performance] suppressed: {e}\n")
298
- except Exception:
299
- ...
300
-
301
-
302
- def setup_performance_logger() -> logging.Logger:
303
- """Set up performance logging"""
304
- perf_logger = logging.getLogger("performance")
305
-
306
- # Add handler if not already configured
307
- if not perf_logger.handlers:
308
- handler = SafeStreamHandler()
309
- formatter = logging.Formatter("%(asctime)s - Performance - %(message)s")
310
- handler.setFormatter(formatter)
311
- perf_logger.addHandler(handler)
312
- perf_logger.setLevel(logging.INFO)
313
-
314
- return perf_logger
315
-
316
-
317
- class LoggingContext:
318
- """Context manager for controlling logging behavior"""
319
-
320
- def __init__(self, enabled: bool = True, level: int | None = None):
321
- self.enabled = enabled
322
- self.level = level
323
- self.old_level: int | None = None
324
- self.target_logger = (
325
- logging.getLogger()
326
- ) # Use root logger for compatibility with tests
327
-
328
- def __enter__(self) -> "LoggingContext":
329
- if self.enabled and self.level is not None:
330
- self.old_level = self.target_logger.level
331
- self.target_logger.setLevel(self.level)
332
- return self
333
-
334
- def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
335
- if self.enabled and self.old_level is not None:
336
- self.target_logger.setLevel(self.old_level)
1
+ #!/usr/bin/env python3
2
+ """
3
+ Utilities for Tree-sitter Analyzer
4
+
5
+ Provides logging, debugging, and common utility functions.
6
+ """
7
+
8
+ import atexit
9
+ import logging
10
+ import os
11
+ import sys
12
+ from functools import wraps
13
+ from typing import Any
14
+
15
+
16
+ # Configure global logger
17
+ def setup_logger(
18
+ name: str = "tree_sitter_analyzer", level: int = logging.WARNING
19
+ ) -> logging.Logger:
20
+ """Setup unified logger for the project"""
21
+ # Get log level from environment variable
22
+ env_level = os.environ.get("LOG_LEVEL", "").upper()
23
+ if env_level == "DEBUG":
24
+ level = logging.DEBUG
25
+ elif env_level == "INFO":
26
+ level = logging.INFO
27
+ elif env_level == "WARNING":
28
+ level = logging.WARNING
29
+ elif env_level == "ERROR":
30
+ level = logging.ERROR
31
+
32
+ logger = logging.getLogger(name)
33
+
34
+ if not logger.handlers: # Avoid duplicate handlers
35
+ # Create a safe handler that writes to stderr to avoid breaking MCP stdio
36
+ handler = SafeStreamHandler()
37
+ formatter = logging.Formatter(
38
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
39
+ )
40
+ handler.setFormatter(formatter)
41
+ logger.addHandler(handler)
42
+
43
+ # Also log to a local file for debugging when launched by clients (e.g., Cursor)
44
+ # This helps diagnose cases where stdio is captured by the client and logs are hidden.
45
+ try:
46
+ file_handler = logging.FileHandler(
47
+ "cursor_mcp_server.log", encoding="utf-8"
48
+ )
49
+ file_handler.setFormatter(formatter)
50
+ logger.addHandler(file_handler)
51
+ except Exception as e:
52
+ # Never let logging configuration break runtime behavior; log to stderr if possible
53
+ if hasattr(sys, "stderr") and hasattr(sys.stderr, "write"):
54
+ try:
55
+ sys.stderr.write(
56
+ f"[logging_setup] file handler init skipped: {e}\n"
57
+ )
58
+ except Exception:
59
+ ...
60
+
61
+ logger.setLevel(level)
62
+
63
+ return logger
64
+
65
+
66
+ class SafeStreamHandler(logging.StreamHandler):
67
+ """
68
+ A StreamHandler that safely handles closed streams
69
+ """
70
+
71
+ def __init__(self, stream=None):
72
+ # Default to sys.stderr to keep stdout clean for MCP stdio
73
+ super().__init__(stream if stream is not None else sys.stderr)
74
+
75
+ def emit(self, record: Any) -> None:
76
+ """
77
+ Emit a record, safely handling closed streams
78
+ """
79
+ try:
80
+ # Check if stream is closed before writing
81
+ if hasattr(self.stream, "closed") and self.stream.closed:
82
+ return
83
+
84
+ # Check if we can write to the stream
85
+ if not hasattr(self.stream, "write"):
86
+ return
87
+
88
+ super().emit(record)
89
+ except (ValueError, OSError, AttributeError):
90
+ # Silently ignore I/O errors during shutdown
91
+ pass
92
+ except Exception:
93
+ # For any other unexpected errors, use handleError
94
+ self.handleError(record)
95
+
96
+
97
+ def setup_safe_logging_shutdown() -> None:
98
+ """
99
+ Setup safe logging shutdown to prevent I/O errors
100
+ """
101
+
102
+ def cleanup_logging() -> None:
103
+ """Clean up logging handlers safely"""
104
+ try:
105
+ # Get all loggers
106
+ loggers = [logging.getLogger()] + [
107
+ logging.getLogger(name) for name in logging.Logger.manager.loggerDict
108
+ ]
109
+
110
+ for logger in loggers:
111
+ for handler in logger.handlers[:]:
112
+ try:
113
+ handler.close()
114
+ logger.removeHandler(handler)
115
+ except Exception as e:
116
+ if hasattr(sys, "stderr") and hasattr(sys.stderr, "write"):
117
+ try:
118
+ sys.stderr.write(
119
+ f"[logging_cleanup] handler close/remove skipped: {e}\n"
120
+ )
121
+ except Exception:
122
+ ...
123
+ except Exception as e:
124
+ if hasattr(sys, "stderr") and hasattr(sys.stderr, "write"):
125
+ try:
126
+ sys.stderr.write(f"[logging_cleanup] cleanup skipped: {e}\n")
127
+ except Exception:
128
+ ...
129
+
130
+ # Register cleanup function
131
+ atexit.register(cleanup_logging)
132
+
133
+
134
+ # Setup safe shutdown on import
135
+ setup_safe_logging_shutdown()
136
+
137
+
138
+ # Global logger instance
139
+ logger = setup_logger()
140
+
141
+
142
+ def log_info(message: str, *args: Any, **kwargs: Any) -> None:
143
+ """Log info message"""
144
+ try:
145
+ logger.info(message, *args, **kwargs)
146
+ except (ValueError, OSError) as e:
147
+ if hasattr(sys, "stderr") and hasattr(sys.stderr, "write"):
148
+ try:
149
+ sys.stderr.write(f"[log_info] suppressed: {e}\n")
150
+ except Exception:
151
+ ...
152
+
153
+
154
+ def log_warning(message: str, *args: Any, **kwargs: Any) -> None:
155
+ """Log warning message"""
156
+ try:
157
+ logger.warning(message, *args, **kwargs)
158
+ except (ValueError, OSError) as e:
159
+ if hasattr(sys, "stderr") and hasattr(sys.stderr, "write"):
160
+ try:
161
+ sys.stderr.write(f"[log_warning] suppressed: {e}\n")
162
+ except Exception:
163
+ ...
164
+
165
+
166
+ def log_error(message: str, *args: Any, **kwargs: Any) -> None:
167
+ """Log error message"""
168
+ try:
169
+ logger.error(message, *args, **kwargs)
170
+ except (ValueError, OSError) as e:
171
+ if hasattr(sys, "stderr") and hasattr(sys.stderr, "write"):
172
+ try:
173
+ sys.stderr.write(f"[log_error] suppressed: {e}\n")
174
+ except Exception:
175
+ ...
176
+
177
+
178
+ def log_debug(message: str, *args: Any, **kwargs: Any) -> None:
179
+ """Log debug message"""
180
+ try:
181
+ logger.debug(message, *args, **kwargs)
182
+ except (ValueError, OSError) as e:
183
+ if hasattr(sys, "stderr") and hasattr(sys.stderr, "write"):
184
+ try:
185
+ sys.stderr.write(f"[log_debug] suppressed: {e}\n")
186
+ except Exception:
187
+ ...
188
+
189
+
190
+ def suppress_output(func: Any) -> Any:
191
+ """Decorator to suppress print statements in production"""
192
+
193
+ @wraps(func)
194
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
195
+ # Check if we're in test/debug mode
196
+ if getattr(sys, "_testing", False):
197
+ return func(*args, **kwargs)
198
+
199
+ # Redirect stdout to suppress prints
200
+ old_stdout = sys.stdout
201
+ try:
202
+ sys.stdout = (
203
+ open("/dev/null", "w") if sys.platform != "win32" else open("nul", "w")
204
+ )
205
+ result = func(*args, **kwargs)
206
+ finally:
207
+ try:
208
+ sys.stdout.close()
209
+ except Exception as e:
210
+ if hasattr(sys, "stderr") and hasattr(sys.stderr, "write"):
211
+ try:
212
+ sys.stderr.write(
213
+ f"[suppress_output] stdout close suppressed: {e}\n"
214
+ )
215
+ except Exception:
216
+ ...
217
+ sys.stdout = old_stdout
218
+
219
+ return result
220
+
221
+ return wrapper
222
+
223
+
224
+ class QuietMode:
225
+ """Context manager for quiet execution"""
226
+
227
+ def __init__(self, enabled: bool = True):
228
+ self.enabled = enabled
229
+ self.old_level: int | None = None
230
+
231
+ def __enter__(self) -> "QuietMode":
232
+ if self.enabled:
233
+ self.old_level = logger.level
234
+ logger.setLevel(logging.ERROR)
235
+ return self
236
+
237
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
238
+ if self.enabled and self.old_level is not None:
239
+ logger.setLevel(self.old_level)
240
+
241
+
242
+ def safe_print(message: str, level: str = "info", quiet: bool = False) -> None:
243
+ """Safe print function that can be controlled"""
244
+ if quiet:
245
+ return
246
+
247
+ level_map = {
248
+ "info": log_info,
249
+ "warning": log_warning,
250
+ "error": log_error,
251
+ "debug": log_debug,
252
+ }
253
+
254
+ log_func = level_map.get(level.lower(), log_info)
255
+ log_func(message)
256
+
257
+
258
+ def create_performance_logger(name: str) -> logging.Logger:
259
+ """Create performance-focused logger"""
260
+ perf_logger = logging.getLogger(f"{name}.performance")
261
+
262
+ if not perf_logger.handlers:
263
+ handler = SafeStreamHandler()
264
+ formatter = logging.Formatter("%(asctime)s - PERF - %(message)s")
265
+ handler.setFormatter(formatter)
266
+ perf_logger.addHandler(handler)
267
+ perf_logger.setLevel(logging.DEBUG) # Change to DEBUG level
268
+
269
+ return perf_logger
270
+
271
+
272
+ # Performance logger instance
273
+ perf_logger = create_performance_logger("tree_sitter_analyzer")
274
+
275
+
276
+ def log_performance(
277
+ operation: str,
278
+ execution_time: float | None = None,
279
+ details: dict[Any, Any] | str | None = None,
280
+ ) -> None:
281
+ """Log performance metrics"""
282
+ try:
283
+ message = f"{operation}"
284
+ if execution_time is not None:
285
+ message += f": {execution_time:.4f}s"
286
+ if details:
287
+ if isinstance(details, dict):
288
+ detail_str = ", ".join([f"{k}: {v}" for k, v in details.items()])
289
+ else:
290
+ detail_str = str(details)
291
+ message += f" - {detail_str}"
292
+ perf_logger.debug(message) # Change to DEBUG level
293
+ except (ValueError, OSError) as e:
294
+ if hasattr(sys, "stderr") and hasattr(sys.stderr, "write"):
295
+ try:
296
+ sys.stderr.write(f"[log_performance] suppressed: {e}\n")
297
+ except Exception:
298
+ ...
299
+
300
+
301
+ def setup_performance_logger() -> logging.Logger:
302
+ """Set up performance logging"""
303
+ perf_logger = logging.getLogger("performance")
304
+
305
+ # Add handler if not already configured
306
+ if not perf_logger.handlers:
307
+ handler = SafeStreamHandler()
308
+ formatter = logging.Formatter("%(asctime)s - Performance - %(message)s")
309
+ handler.setFormatter(formatter)
310
+ perf_logger.addHandler(handler)
311
+ perf_logger.setLevel(logging.INFO)
312
+
313
+ return perf_logger
314
+
315
+
316
+ class LoggingContext:
317
+ """Context manager for controlling logging behavior"""
318
+
319
+ def __init__(self, enabled: bool = True, level: int | None = None):
320
+ self.enabled = enabled
321
+ self.level = level
322
+ self.old_level: int | None = None
323
+ self.target_logger = (
324
+ logging.getLogger()
325
+ ) # Use root logger for compatibility with tests
326
+
327
+ def __enter__(self) -> "LoggingContext":
328
+ if self.enabled and self.level is not None:
329
+ self.old_level = self.target_logger.level
330
+ self.target_logger.setLevel(self.level)
331
+ return self
332
+
333
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
334
+ if self.enabled and self.old_level is not None:
335
+ self.target_logger.setLevel(self.old_level)