tree-sitter-analyzer 0.2.0__py3-none-any.whl → 0.3.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 +133 -121
  2. tree_sitter_analyzer/__main__.py +11 -12
  3. tree_sitter_analyzer/api.py +531 -539
  4. tree_sitter_analyzer/cli/__init__.py +39 -39
  5. tree_sitter_analyzer/cli/__main__.py +12 -13
  6. tree_sitter_analyzer/cli/commands/__init__.py +26 -27
  7. tree_sitter_analyzer/cli/commands/advanced_command.py +88 -88
  8. tree_sitter_analyzer/cli/commands/base_command.py +160 -155
  9. tree_sitter_analyzer/cli/commands/default_command.py +18 -19
  10. tree_sitter_analyzer/cli/commands/partial_read_command.py +141 -133
  11. tree_sitter_analyzer/cli/commands/query_command.py +81 -82
  12. tree_sitter_analyzer/cli/commands/structure_command.py +138 -121
  13. tree_sitter_analyzer/cli/commands/summary_command.py +101 -93
  14. tree_sitter_analyzer/cli/commands/table_command.py +232 -233
  15. tree_sitter_analyzer/cli/info_commands.py +120 -121
  16. tree_sitter_analyzer/cli_main.py +277 -276
  17. tree_sitter_analyzer/core/__init__.py +15 -20
  18. tree_sitter_analyzer/core/analysis_engine.py +591 -574
  19. tree_sitter_analyzer/core/cache_service.py +320 -330
  20. tree_sitter_analyzer/core/engine.py +557 -560
  21. tree_sitter_analyzer/core/parser.py +293 -288
  22. tree_sitter_analyzer/core/query.py +494 -502
  23. tree_sitter_analyzer/encoding_utils.py +458 -460
  24. tree_sitter_analyzer/exceptions.py +337 -340
  25. tree_sitter_analyzer/file_handler.py +217 -222
  26. tree_sitter_analyzer/formatters/__init__.py +1 -1
  27. tree_sitter_analyzer/formatters/base_formatter.py +167 -168
  28. tree_sitter_analyzer/formatters/formatter_factory.py +78 -74
  29. tree_sitter_analyzer/formatters/java_formatter.py +287 -270
  30. tree_sitter_analyzer/formatters/python_formatter.py +255 -235
  31. tree_sitter_analyzer/interfaces/__init__.py +9 -10
  32. tree_sitter_analyzer/interfaces/cli.py +528 -557
  33. tree_sitter_analyzer/interfaces/cli_adapter.py +322 -319
  34. tree_sitter_analyzer/interfaces/mcp_adapter.py +180 -170
  35. tree_sitter_analyzer/interfaces/mcp_server.py +405 -416
  36. tree_sitter_analyzer/java_analyzer.py +218 -219
  37. tree_sitter_analyzer/language_detector.py +398 -400
  38. tree_sitter_analyzer/language_loader.py +224 -228
  39. tree_sitter_analyzer/languages/__init__.py +10 -11
  40. tree_sitter_analyzer/languages/java_plugin.py +1129 -1113
  41. tree_sitter_analyzer/languages/python_plugin.py +737 -712
  42. tree_sitter_analyzer/mcp/__init__.py +31 -32
  43. tree_sitter_analyzer/mcp/resources/__init__.py +44 -47
  44. tree_sitter_analyzer/mcp/resources/code_file_resource.py +212 -213
  45. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +560 -550
  46. tree_sitter_analyzer/mcp/server.py +333 -345
  47. tree_sitter_analyzer/mcp/tools/__init__.py +30 -31
  48. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +621 -557
  49. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +242 -245
  50. tree_sitter_analyzer/mcp/tools/base_tool.py +54 -55
  51. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +300 -302
  52. tree_sitter_analyzer/mcp/tools/table_format_tool.py +362 -359
  53. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +543 -476
  54. tree_sitter_analyzer/mcp/utils/__init__.py +105 -106
  55. tree_sitter_analyzer/mcp/utils/error_handler.py +549 -549
  56. tree_sitter_analyzer/models.py +470 -481
  57. tree_sitter_analyzer/output_manager.py +261 -264
  58. tree_sitter_analyzer/plugins/__init__.py +333 -334
  59. tree_sitter_analyzer/plugins/base.py +477 -446
  60. tree_sitter_analyzer/plugins/java_plugin.py +608 -625
  61. tree_sitter_analyzer/plugins/javascript_plugin.py +446 -439
  62. tree_sitter_analyzer/plugins/manager.py +362 -355
  63. tree_sitter_analyzer/plugins/plugin_loader.py +85 -83
  64. tree_sitter_analyzer/plugins/python_plugin.py +606 -598
  65. tree_sitter_analyzer/plugins/registry.py +374 -366
  66. tree_sitter_analyzer/queries/__init__.py +26 -27
  67. tree_sitter_analyzer/queries/java.py +391 -394
  68. tree_sitter_analyzer/queries/javascript.py +148 -149
  69. tree_sitter_analyzer/queries/python.py +285 -286
  70. tree_sitter_analyzer/queries/typescript.py +229 -230
  71. tree_sitter_analyzer/query_loader.py +254 -260
  72. tree_sitter_analyzer/table_formatter.py +468 -448
  73. tree_sitter_analyzer/utils.py +277 -277
  74. {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.3.0.dist-info}/METADATA +21 -6
  75. tree_sitter_analyzer-0.3.0.dist-info/RECORD +77 -0
  76. tree_sitter_analyzer-0.2.0.dist-info/RECORD +0 -77
  77. {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.3.0.dist-info}/WHEEL +0 -0
  78. {tree_sitter_analyzer-0.2.0.dist-info → tree_sitter_analyzer-0.3.0.dist-info}/entry_points.txt +0 -0
@@ -1,557 +1,528 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- Command Line Interface
5
-
6
- New CLI implementation that uses the API facade for all operations.
7
- Provides a clean separation between CLI concerns and core analysis logic.
8
- """
9
-
10
- import argparse
11
- import json
12
- import sys
13
- from pathlib import Path
14
- from typing import Any, Dict, List, Optional
15
- import logging
16
-
17
- from .. import api
18
- from ..utils import log_error, log_info, log_warning
19
-
20
- # Configure logging for CLI
21
- logging.basicConfig(
22
- level=logging.INFO,
23
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
24
- )
25
- logger = logging.getLogger(__name__)
26
-
27
-
28
- def create_parser() -> argparse.ArgumentParser:
29
- """Create and configure the argument parser."""
30
- parser = argparse.ArgumentParser(
31
- prog="tree-sitter-analyzer",
32
- description="Extensible multi-language code analyzer using Tree-sitter",
33
- formatter_class=argparse.RawDescriptionHelpFormatter,
34
- epilog="""
35
- Examples:
36
- # Analyze a Java file
37
- tree-sitter-analyzer analyze example.java
38
-
39
- # Analyze with specific language
40
- tree-sitter-analyzer analyze --language python script.py
41
-
42
- # Execute specific queries
43
- tree-sitter-analyzer analyze --queries functions,classes example.java
44
-
45
- # Extract only code elements
46
- tree-sitter-analyzer extract example.py
47
-
48
- # List supported languages
49
- tree-sitter-analyzer languages
50
-
51
- # Get framework information
52
- tree-sitter-analyzer info
53
-
54
- For more information, visit: https://github.com/aimasteracc/tree-sitter-analyzer
55
- """
56
- )
57
-
58
- # Global options
59
- parser.add_argument(
60
- "--version",
61
- action="version",
62
- version="tree-sitter-analyzer 0.0.1"
63
- )
64
- parser.add_argument(
65
- "--verbose", "-v",
66
- action="store_true",
67
- help="Enable verbose output"
68
- )
69
- parser.add_argument(
70
- "--quiet", "-q",
71
- action="store_true",
72
- help="Suppress non-essential output"
73
- )
74
- parser.add_argument(
75
- "--output", "-o",
76
- choices=["json", "text", "table"],
77
- default="text",
78
- help="Output format (default: text)"
79
- )
80
-
81
- # Subcommands
82
- subparsers = parser.add_subparsers(dest="command", help="Available commands")
83
-
84
- # Analyze command
85
- analyze_parser = subparsers.add_parser(
86
- "analyze",
87
- help="Analyze source code files",
88
- description="Perform comprehensive analysis of source code files"
89
- )
90
- analyze_parser.add_argument(
91
- "file_path",
92
- help="Path to the source file to analyze"
93
- )
94
- analyze_parser.add_argument(
95
- "--language", "-l",
96
- help="Programming language (auto-detected if not specified)"
97
- )
98
- analyze_parser.add_argument(
99
- "--queries",
100
- help="Comma-separated list of queries to execute"
101
- )
102
- analyze_parser.add_argument(
103
- "--no-elements",
104
- action="store_true",
105
- help="Skip code element extraction"
106
- )
107
- analyze_parser.add_argument(
108
- "--no-queries",
109
- action="store_true",
110
- help="Skip query execution"
111
- )
112
-
113
- # Extract command
114
- extract_parser = subparsers.add_parser(
115
- "extract",
116
- help="Extract code elements from files",
117
- description="Extract specific code elements like functions, classes, etc."
118
- )
119
- extract_parser.add_argument(
120
- "file_path",
121
- help="Path to the source file"
122
- )
123
- extract_parser.add_argument(
124
- "--language", "-l",
125
- help="Programming language (auto-detected if not specified)"
126
- )
127
- extract_parser.add_argument(
128
- "--types",
129
- help="Comma-separated list of element types to extract (e.g., functions,classes)"
130
- )
131
-
132
- # Query command
133
- query_parser = subparsers.add_parser(
134
- "query",
135
- help="Execute specific queries on files",
136
- description="Execute specific tree-sitter queries on source files"
137
- )
138
- query_parser.add_argument(
139
- "file_path",
140
- help="Path to the source file"
141
- )
142
- query_parser.add_argument(
143
- "query_name",
144
- help="Name of the query to execute"
145
- )
146
- query_parser.add_argument(
147
- "--language", "-l",
148
- help="Programming language (auto-detected if not specified)"
149
- )
150
-
151
- # Validate command
152
- validate_parser = subparsers.add_parser(
153
- "validate",
154
- help="Validate source files",
155
- description="Check if files can be parsed and analyzed"
156
- )
157
- validate_parser.add_argument(
158
- "file_path",
159
- help="Path to the source file to validate"
160
- )
161
-
162
- # Languages command
163
- languages_parser = subparsers.add_parser(
164
- "languages",
165
- help="List supported languages",
166
- description="Show all supported programming languages and their extensions"
167
- )
168
- languages_parser.add_argument(
169
- "--extensions",
170
- action="store_true",
171
- help="Show file extensions for each language"
172
- )
173
-
174
- # Info command
175
- info_parser = subparsers.add_parser(
176
- "info",
177
- help="Show framework information",
178
- description="Display information about the analyzer framework"
179
- )
180
-
181
- # Queries command
182
- queries_parser = subparsers.add_parser(
183
- "queries",
184
- help="List available queries",
185
- description="Show available queries for a specific language"
186
- )
187
- queries_parser.add_argument(
188
- "language",
189
- help="Programming language name"
190
- )
191
-
192
- return parser
193
-
194
-
195
- def handle_analyze_command(args: argparse.Namespace) -> int:
196
- """Handle the analyze command."""
197
- try:
198
- file_path = Path(args.file_path)
199
-
200
- if not file_path.exists():
201
- print(f"Error: File '{file_path}' does not exist", file=sys.stderr)
202
- return 1
203
-
204
- # Parse queries if provided
205
- queries = None
206
- if args.queries:
207
- queries = [q.strip() for q in args.queries.split(",")]
208
-
209
- # Perform analysis
210
- result = api.analyze_file(
211
- file_path=file_path,
212
- language=args.language,
213
- queries=queries,
214
- include_elements=not args.no_elements,
215
- include_queries=not args.no_queries
216
- )
217
-
218
- # Output results
219
- if args.output == "json":
220
- print(json.dumps(result, indent=2))
221
- else:
222
- format_analysis_output(result, args.output)
223
-
224
- return 0 if result.get("success", False) else 1
225
-
226
- except Exception as e:
227
- print(f"Error during analysis: {e}", file=sys.stderr)
228
- return 1
229
-
230
-
231
- def handle_extract_command(args: argparse.Namespace) -> int:
232
- """Handle the extract command."""
233
- try:
234
- file_path = Path(args.file_path)
235
-
236
- if not file_path.exists():
237
- print(f"Error: File '{file_path}' does not exist", file=sys.stderr)
238
- return 1
239
-
240
- # Parse element types if provided
241
- element_types = None
242
- if args.types:
243
- element_types = [t.strip() for t in args.types.split(",")]
244
-
245
- # Extract elements
246
- result = api.extract_elements(
247
- file_path=file_path,
248
- language=args.language,
249
- element_types=element_types
250
- )
251
-
252
- # Output results
253
- if args.output == "json":
254
- print(json.dumps(result, indent=2))
255
- else:
256
- format_extraction_output(result, args.output)
257
-
258
- return 0 if result.get("success", False) else 1
259
-
260
- except Exception as e:
261
- print(f"Error during extraction: {e}", file=sys.stderr)
262
- return 1
263
-
264
-
265
- def handle_query_command(args: argparse.Namespace) -> int:
266
- """Handle the query command."""
267
- try:
268
- file_path = Path(args.file_path)
269
-
270
- if not file_path.exists():
271
- print(f"Error: File '{file_path}' does not exist", file=sys.stderr)
272
- return 1
273
-
274
- # Execute query
275
- result = api.execute_query(
276
- file_path=file_path,
277
- query_name=args.query_name,
278
- language=args.language
279
- )
280
-
281
- # Output results
282
- if args.output == "json":
283
- print(json.dumps(result, indent=2))
284
- else:
285
- format_query_output(result, args.output)
286
-
287
- return 0 if result.get("success", False) else 1
288
-
289
- except Exception as e:
290
- print(f"Error during query execution: {e}", file=sys.stderr)
291
- return 1
292
-
293
-
294
- def handle_validate_command(args: argparse.Namespace) -> int:
295
- """Handle the validate command."""
296
- try:
297
- file_path = Path(args.file_path)
298
-
299
- # Validate file
300
- result = api.validate_file(file_path)
301
-
302
- # Output results
303
- if args.output == "json":
304
- print(json.dumps(result, indent=2))
305
- else:
306
- format_validation_output(result, args.output)
307
-
308
- return 0 if result.get("valid", False) else 1
309
-
310
- except Exception as e:
311
- print(f"Error during validation: {e}", file=sys.stderr)
312
- return 1
313
-
314
-
315
- def handle_languages_command(args: argparse.Namespace) -> int:
316
- """Handle the languages command."""
317
- try:
318
- languages = api.get_supported_languages()
319
-
320
- if args.output == "json":
321
- if args.extensions:
322
- lang_info = {}
323
- for lang in languages:
324
- extensions = api.get_file_extensions(lang)
325
- lang_info[lang] = extensions
326
- print(json.dumps(lang_info, indent=2))
327
- else:
328
- print(json.dumps(languages, indent=2))
329
- else:
330
- print("Supported Languages:")
331
- print("=" * 20)
332
- for lang in sorted(languages):
333
- if args.extensions:
334
- extensions = api.get_file_extensions(lang)
335
- ext_str = ", ".join(extensions) if extensions else "No extensions"
336
- print(f" {lang:<12} - {ext_str}")
337
- else:
338
- print(f" {lang}")
339
-
340
- if not args.extensions:
341
- print(f"\nTotal: {len(languages)} languages")
342
- print("Use --extensions to see file extensions for each language")
343
-
344
- return 0
345
-
346
- except Exception as e:
347
- print(f"Error getting language information: {e}", file=sys.stderr)
348
- return 1
349
-
350
-
351
- def handle_info_command(args: argparse.Namespace) -> int:
352
- """Handle the info command."""
353
- try:
354
- info = api.get_framework_info()
355
-
356
- if args.output == "json":
357
- print(json.dumps(info, indent=2))
358
- else:
359
- print("Tree-sitter Analyzer Framework Information")
360
- print("=" * 45)
361
- print(f"Name: {info.get('name', 'Unknown')}")
362
- print(f"Version: {info.get('version', 'Unknown')}")
363
- print(f"Supported Languages: {info.get('total_languages', 0)}")
364
-
365
- languages = info.get('supported_languages', [])
366
- if languages:
367
- print(f"Languages: {', '.join(sorted(languages))}")
368
-
369
- components = info.get('core_components', [])
370
- if components:
371
- print(f"Core Components: {', '.join(components)}")
372
-
373
- return 0
374
-
375
- except Exception as e:
376
- print(f"Error getting framework information: {e}", file=sys.stderr)
377
- return 1
378
-
379
-
380
- def handle_queries_command(args: argparse.Namespace) -> int:
381
- """Handle the queries command."""
382
- try:
383
- if not api.is_language_supported(args.language):
384
- print(f"Error: Language '{args.language}' is not supported", file=sys.stderr)
385
- return 1
386
-
387
- queries = api.get_available_queries(args.language)
388
-
389
- if args.output == "json":
390
- print(json.dumps(queries, indent=2))
391
- else:
392
- print(f"Available Queries for {args.language}:")
393
- print("=" * (25 + len(args.language)))
394
- for query in sorted(queries):
395
- print(f" {query}")
396
-
397
- print(f"\nTotal: {len(queries)} queries")
398
-
399
- return 0
400
-
401
- except Exception as e:
402
- print(f"Error getting query information: {e}", file=sys.stderr)
403
- return 1
404
-
405
-
406
- def format_analysis_output(result: Dict[str, Any], output_format: str) -> None:
407
- """Format and display analysis results."""
408
- if not result.get("success", False):
409
- print(f"Analysis failed: {result.get('error', 'Unknown error')}", file=sys.stderr)
410
- return
411
-
412
- print("Analysis Results")
413
- print("=" * 16)
414
-
415
- # File information
416
- file_info = result.get("file_info", {})
417
- print(f"File: {file_info.get('path', 'Unknown')}")
418
-
419
- # Language information
420
- lang_info = result.get("language_info", {})
421
- language = lang_info.get("language", "Unknown")
422
- auto_detected = lang_info.get("auto_detected", False)
423
- detection_str = " (auto-detected)" if auto_detected else ""
424
- print(f"Language: {language}{detection_str}")
425
-
426
- # AST information
427
- ast_info = result.get("ast_info", {})
428
- print(f"Source Lines: {ast_info.get('source_lines', 0)}")
429
- print(f"AST Nodes: {ast_info.get('node_count', 0)}")
430
-
431
- # Query results
432
- query_results = result.get("query_results", {})
433
- if query_results:
434
- print(f"\nQuery Results:")
435
- for query_name, matches in query_results.items():
436
- print(f" {query_name}: {len(matches)} matches")
437
-
438
- # Elements
439
- elements = result.get("elements", [])
440
- if elements:
441
- print(f"\nCode Elements: {len(elements)} found")
442
- element_types = {}
443
- for element in elements:
444
- elem_type = element.get("type", "unknown")
445
- element_types[elem_type] = element_types.get(elem_type, 0) + 1
446
-
447
- for elem_type, count in sorted(element_types.items()):
448
- print(f" {elem_type}: {count}")
449
-
450
-
451
- def format_extraction_output(result: Dict[str, Any], output_format: str) -> None:
452
- """Format and display extraction results."""
453
- if not result.get("success", False):
454
- print(f"Extraction failed: {result.get('error', 'Unknown error')}", file=sys.stderr)
455
- return
456
-
457
- elements = result.get("elements", [])
458
- language = result.get("language", "Unknown")
459
-
460
- print("Code Element Extraction Results")
461
- print("=" * 31)
462
- print(f"File: {result.get('file_path', 'Unknown')}")
463
- print(f"Language: {language}")
464
- print(f"Elements Found: {len(elements)}")
465
-
466
- if elements:
467
- print("\nElements:")
468
- for element in elements:
469
- name = element.get("name", "Unknown")
470
- elem_type = element.get("type", "unknown")
471
- start_line = element.get("start_line", 0)
472
- print(f" {elem_type}: {name} (line {start_line})")
473
-
474
-
475
- def format_query_output(result: Dict[str, Any], output_format: str) -> None:
476
- """Format and display query results."""
477
- if not result.get("success", False):
478
- print(f"Query failed: {result.get('error', 'Unknown error')}", file=sys.stderr)
479
- return
480
-
481
- query_name = result.get("query_name", "Unknown")
482
- matches = result.get("results", [])
483
- language = result.get("language", "Unknown")
484
-
485
- print("Query Execution Results")
486
- print("=" * 23)
487
- print(f"File: {result.get('file_path', 'Unknown')}")
488
- print(f"Language: {language}")
489
- print(f"Query: {query_name}")
490
- print(f"Matches: {len(matches)}")
491
-
492
- if matches:
493
- print("\nMatches:")
494
- for i, match in enumerate(matches, 1):
495
- start_line = match.get("start_line", 0)
496
- content = match.get("content", "").strip()
497
- if len(content) > 50:
498
- content = content[:47] + "..."
499
- print(f" {i}. Line {start_line}: {content}")
500
-
501
-
502
- def format_validation_output(result: Dict[str, Any], output_format: str) -> None:
503
- """Format and display validation results."""
504
- valid = result.get("valid", False)
505
- exists = result.get("exists", False)
506
- readable = result.get("readable", False)
507
- language = result.get("language")
508
- supported = result.get("supported", False)
509
- errors = result.get("errors", [])
510
-
511
- print("File Validation Results")
512
- print("=" * 23)
513
- print(f"Valid: {'✓' if valid else '✗'}")
514
- print(f"Exists: {'✓' if exists else '✗'}")
515
- print(f"Readable: {'✓' if readable else '✗'}")
516
- print(f"Language: {language or 'Unknown'}")
517
- print(f"Supported: {'✓' if supported else '✗'}")
518
-
519
- if errors:
520
- print("\nErrors:")
521
- for error in errors:
522
- print(f" - {error}")
523
-
524
-
525
- def main() -> int:
526
- """Main CLI entry point."""
527
- parser = create_parser()
528
- args = parser.parse_args()
529
-
530
- # Configure logging based on verbosity
531
- if args.quiet:
532
- logging.getLogger().setLevel(logging.ERROR)
533
- elif args.verbose:
534
- logging.getLogger().setLevel(logging.DEBUG)
535
-
536
- # Handle commands
537
- if args.command == "analyze":
538
- return handle_analyze_command(args)
539
- elif args.command == "extract":
540
- return handle_extract_command(args)
541
- elif args.command == "query":
542
- return handle_query_command(args)
543
- elif args.command == "validate":
544
- return handle_validate_command(args)
545
- elif args.command == "languages":
546
- return handle_languages_command(args)
547
- elif args.command == "info":
548
- return handle_info_command(args)
549
- elif args.command == "queries":
550
- return handle_queries_command(args)
551
- else:
552
- parser.print_help()
553
- return 1
554
-
555
-
556
- if __name__ == "__main__":
557
- sys.exit(main())
1
+ #!/usr/bin/env python3
2
+ """
3
+ Command Line Interface
4
+
5
+ New CLI implementation that uses the API facade for all operations.
6
+ Provides a clean separation between CLI concerns and core analysis logic.
7
+ """
8
+
9
+ import argparse
10
+ import json
11
+ import logging
12
+ import sys
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ from .. import api
17
+
18
+ # Configure logging for CLI
19
+ logging.basicConfig(
20
+ level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
21
+ )
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ def create_parser() -> argparse.ArgumentParser:
26
+ """Create and configure the argument parser."""
27
+ parser = argparse.ArgumentParser(
28
+ prog="tree-sitter-analyzer",
29
+ description="Extensible multi-language code analyzer using Tree-sitter",
30
+ formatter_class=argparse.RawDescriptionHelpFormatter,
31
+ epilog="""
32
+ Examples:
33
+ # Analyze a Java file
34
+ tree-sitter-analyzer analyze example.java
35
+
36
+ # Analyze with specific language
37
+ tree-sitter-analyzer analyze --language python script.py
38
+
39
+ # Execute specific queries
40
+ tree-sitter-analyzer analyze --queries functions,classes example.java
41
+
42
+ # Extract only code elements
43
+ tree-sitter-analyzer extract example.py
44
+
45
+ # List supported languages
46
+ tree-sitter-analyzer languages
47
+
48
+ # Get framework information
49
+ tree-sitter-analyzer info
50
+
51
+ For more information, visit: https://github.com/aimasteracc/tree-sitter-analyzer
52
+ """,
53
+ )
54
+
55
+ # Global options
56
+ parser.add_argument(
57
+ "--version", action="version", version="tree-sitter-analyzer 0.0.1"
58
+ )
59
+ parser.add_argument(
60
+ "--verbose", "-v", action="store_true", help="Enable verbose output"
61
+ )
62
+ parser.add_argument(
63
+ "--quiet", "-q", action="store_true", help="Suppress non-essential output"
64
+ )
65
+ parser.add_argument(
66
+ "--output",
67
+ "-o",
68
+ choices=["json", "text", "table"],
69
+ default="text",
70
+ help="Output format (default: text)",
71
+ )
72
+
73
+ # Subcommands
74
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
75
+
76
+ # Analyze command
77
+ analyze_parser = subparsers.add_parser(
78
+ "analyze",
79
+ help="Analyze source code files",
80
+ description="Perform comprehensive analysis of source code files",
81
+ )
82
+ analyze_parser.add_argument("file_path", help="Path to the source file to analyze")
83
+ analyze_parser.add_argument(
84
+ "--language", "-l", help="Programming language (auto-detected if not specified)"
85
+ )
86
+ analyze_parser.add_argument(
87
+ "--queries", help="Comma-separated list of queries to execute"
88
+ )
89
+ analyze_parser.add_argument(
90
+ "--no-elements", action="store_true", help="Skip code element extraction"
91
+ )
92
+ analyze_parser.add_argument(
93
+ "--no-queries", action="store_true", help="Skip query execution"
94
+ )
95
+
96
+ # Extract command
97
+ extract_parser = subparsers.add_parser(
98
+ "extract",
99
+ help="Extract code elements from files",
100
+ description="Extract specific code elements like functions, classes, etc.",
101
+ )
102
+ extract_parser.add_argument("file_path", help="Path to the source file")
103
+ extract_parser.add_argument(
104
+ "--language", "-l", help="Programming language (auto-detected if not specified)"
105
+ )
106
+ extract_parser.add_argument(
107
+ "--types",
108
+ help="Comma-separated list of element types to extract (e.g., functions,classes)",
109
+ )
110
+
111
+ # Query command
112
+ query_parser = subparsers.add_parser(
113
+ "query",
114
+ help="Execute specific queries on files",
115
+ description="Execute specific tree-sitter queries on source files",
116
+ )
117
+ query_parser.add_argument("file_path", help="Path to the source file")
118
+ query_parser.add_argument("query_name", help="Name of the query to execute")
119
+ query_parser.add_argument(
120
+ "--language", "-l", help="Programming language (auto-detected if not specified)"
121
+ )
122
+
123
+ # Validate command
124
+ validate_parser = subparsers.add_parser(
125
+ "validate",
126
+ help="Validate source files",
127
+ description="Check if files can be parsed and analyzed",
128
+ )
129
+ validate_parser.add_argument(
130
+ "file_path", help="Path to the source file to validate"
131
+ )
132
+
133
+ # Languages command
134
+ languages_parser = subparsers.add_parser(
135
+ "languages",
136
+ help="List supported languages",
137
+ description="Show all supported programming languages and their extensions",
138
+ )
139
+ languages_parser.add_argument(
140
+ "--extensions",
141
+ action="store_true",
142
+ help="Show file extensions for each language",
143
+ )
144
+
145
+ # Info command
146
+ subparsers.add_parser(
147
+ "info",
148
+ help="Show framework information",
149
+ description="Display information about the analyzer framework",
150
+ )
151
+
152
+ # Queries command
153
+ queries_parser = subparsers.add_parser(
154
+ "queries",
155
+ help="List available queries",
156
+ description="Show available queries for a specific language",
157
+ )
158
+ queries_parser.add_argument("language", help="Programming language name")
159
+
160
+ return parser
161
+
162
+
163
+ def handle_analyze_command(args: argparse.Namespace) -> int:
164
+ """Handle the analyze command."""
165
+ try:
166
+ file_path = Path(args.file_path)
167
+
168
+ if not file_path.exists():
169
+ print(f"Error: File '{file_path}' does not exist", file=sys.stderr)
170
+ return 1
171
+
172
+ # Parse queries if provided
173
+ queries = None
174
+ if args.queries:
175
+ queries = [q.strip() for q in args.queries.split(",")]
176
+
177
+ # Perform analysis
178
+ result = api.analyze_file(
179
+ file_path=file_path,
180
+ language=args.language,
181
+ queries=queries,
182
+ include_elements=not args.no_elements,
183
+ include_queries=not args.no_queries,
184
+ )
185
+
186
+ # Output results
187
+ if args.output == "json":
188
+ print(json.dumps(result, indent=2))
189
+ else:
190
+ format_analysis_output(result, args.output)
191
+
192
+ return 0 if result.get("success", False) else 1
193
+
194
+ except Exception as e:
195
+ print(f"Error during analysis: {e}", file=sys.stderr)
196
+ return 1
197
+
198
+
199
+ def handle_extract_command(args: argparse.Namespace) -> int:
200
+ """Handle the extract command."""
201
+ try:
202
+ file_path = Path(args.file_path)
203
+
204
+ if not file_path.exists():
205
+ print(f"Error: File '{file_path}' does not exist", file=sys.stderr)
206
+ return 1
207
+
208
+ # Parse element types if provided
209
+ element_types = None
210
+ if args.types:
211
+ element_types = [t.strip() for t in args.types.split(",")]
212
+
213
+ # Extract elements
214
+ result = api.extract_elements(
215
+ file_path=file_path, language=args.language, element_types=element_types
216
+ )
217
+
218
+ # Output results
219
+ if args.output == "json":
220
+ print(json.dumps(result, indent=2))
221
+ else:
222
+ format_extraction_output(result, args.output)
223
+
224
+ return 0 if result.get("success", False) else 1
225
+
226
+ except Exception as e:
227
+ print(f"Error during extraction: {e}", file=sys.stderr)
228
+ return 1
229
+
230
+
231
+ def handle_query_command(args: argparse.Namespace) -> int:
232
+ """Handle the query command."""
233
+ try:
234
+ file_path = Path(args.file_path)
235
+
236
+ if not file_path.exists():
237
+ print(f"Error: File '{file_path}' does not exist", file=sys.stderr)
238
+ return 1
239
+
240
+ # Execute query
241
+ result = api.execute_query(
242
+ file_path=file_path, query_name=args.query_name, language=args.language
243
+ )
244
+
245
+ # Output results
246
+ if args.output == "json":
247
+ print(json.dumps(result, indent=2))
248
+ else:
249
+ format_query_output(result, args.output)
250
+
251
+ return 0 if result.get("success", False) else 1
252
+
253
+ except Exception as e:
254
+ print(f"Error during query execution: {e}", file=sys.stderr)
255
+ return 1
256
+
257
+
258
+ def handle_validate_command(args: argparse.Namespace) -> int:
259
+ """Handle the validate command."""
260
+ try:
261
+ file_path = Path(args.file_path)
262
+
263
+ # Validate file
264
+ result = api.validate_file(file_path)
265
+
266
+ # Output results
267
+ if args.output == "json":
268
+ print(json.dumps(result, indent=2))
269
+ else:
270
+ format_validation_output(result, args.output)
271
+
272
+ return 0 if result.get("valid", False) else 1
273
+
274
+ except Exception as e:
275
+ print(f"Error during validation: {e}", file=sys.stderr)
276
+ return 1
277
+
278
+
279
+ def handle_languages_command(args: argparse.Namespace) -> int:
280
+ """Handle the languages command."""
281
+ try:
282
+ languages = api.get_supported_languages()
283
+
284
+ if args.output == "json":
285
+ if args.extensions:
286
+ lang_info = {}
287
+ for lang in languages:
288
+ extensions = api.get_file_extensions(lang)
289
+ lang_info[lang] = extensions
290
+ print(json.dumps(lang_info, indent=2))
291
+ else:
292
+ print(json.dumps(languages, indent=2))
293
+ else:
294
+ print("Supported Languages:")
295
+ print("=" * 20)
296
+ for lang in sorted(languages):
297
+ if args.extensions:
298
+ extensions = api.get_file_extensions(lang)
299
+ ext_str = ", ".join(extensions) if extensions else "No extensions"
300
+ print(f" {lang:<12} - {ext_str}")
301
+ else:
302
+ print(f" {lang}")
303
+
304
+ if not args.extensions:
305
+ print(f"\nTotal: {len(languages)} languages")
306
+ print("Use --extensions to see file extensions for each language")
307
+
308
+ return 0
309
+
310
+ except Exception as e:
311
+ print(f"Error getting language information: {e}", file=sys.stderr)
312
+ return 1
313
+
314
+
315
+ def handle_info_command(args: argparse.Namespace) -> int:
316
+ """Handle the info command."""
317
+ try:
318
+ info = api.get_framework_info()
319
+
320
+ if args.output == "json":
321
+ print(json.dumps(info, indent=2))
322
+ else:
323
+ print("Tree-sitter Analyzer Framework Information")
324
+ print("=" * 45)
325
+ print(f"Name: {info.get('name', 'Unknown')}")
326
+ print(f"Version: {info.get('version', 'Unknown')}")
327
+ print(f"Supported Languages: {info.get('total_languages', 0)}")
328
+
329
+ languages = info.get("supported_languages", [])
330
+ if languages:
331
+ print(f"Languages: {', '.join(sorted(languages))}")
332
+
333
+ components = info.get("core_components", [])
334
+ if components:
335
+ print(f"Core Components: {', '.join(components)}")
336
+
337
+ return 0
338
+
339
+ except Exception as e:
340
+ print(f"Error getting framework information: {e}", file=sys.stderr)
341
+ return 1
342
+
343
+
344
+ def handle_queries_command(args: argparse.Namespace) -> int:
345
+ """Handle the queries command."""
346
+ try:
347
+ if not api.is_language_supported(args.language):
348
+ print(
349
+ f"Error: Language '{args.language}' is not supported", file=sys.stderr
350
+ )
351
+ return 1
352
+
353
+ queries = api.get_available_queries(args.language)
354
+
355
+ if args.output == "json":
356
+ print(json.dumps(queries, indent=2))
357
+ else:
358
+ print(f"Available Queries for {args.language}:")
359
+ print("=" * (25 + len(args.language)))
360
+ for query in sorted(queries):
361
+ print(f" {query}")
362
+
363
+ print(f"\nTotal: {len(queries)} queries")
364
+
365
+ return 0
366
+
367
+ except Exception as e:
368
+ print(f"Error getting query information: {e}", file=sys.stderr)
369
+ return 1
370
+
371
+
372
+ def format_analysis_output(result: dict[str, Any], output_format: str) -> None:
373
+ """Format and display analysis results."""
374
+ if not result.get("success", False):
375
+ print(
376
+ f"Analysis failed: {result.get('error', 'Unknown error')}", file=sys.stderr
377
+ )
378
+ return
379
+
380
+ print("Analysis Results")
381
+ print("=" * 16)
382
+
383
+ # File information
384
+ file_info = result.get("file_info", {})
385
+ print(f"File: {file_info.get('path', 'Unknown')}")
386
+
387
+ # Language information
388
+ lang_info = result.get("language_info", {})
389
+ language = lang_info.get("language", "Unknown")
390
+ auto_detected = lang_info.get("auto_detected", False)
391
+ detection_str = " (auto-detected)" if auto_detected else ""
392
+ print(f"Language: {language}{detection_str}")
393
+
394
+ # AST information
395
+ ast_info = result.get("ast_info", {})
396
+ print(f"Source Lines: {ast_info.get('source_lines', 0)}")
397
+ print(f"AST Nodes: {ast_info.get('node_count', 0)}")
398
+
399
+ # Query results
400
+ query_results = result.get("query_results", {})
401
+ if query_results:
402
+ print("\nQuery Results:")
403
+ for query_name, matches in query_results.items():
404
+ print(f" {query_name}: {len(matches)} matches")
405
+
406
+ # Elements
407
+ elements = result.get("elements", [])
408
+ if elements:
409
+ print(f"\nCode Elements: {len(elements)} found")
410
+ element_types = {}
411
+ for element in elements:
412
+ elem_type = element.get("type", "unknown")
413
+ element_types[elem_type] = element_types.get(elem_type, 0) + 1
414
+
415
+ for elem_type, count in sorted(element_types.items()):
416
+ print(f" {elem_type}: {count}")
417
+
418
+
419
+ def format_extraction_output(result: dict[str, Any], output_format: str) -> None:
420
+ """Format and display extraction results."""
421
+ if not result.get("success", False):
422
+ print(
423
+ f"Extraction failed: {result.get('error', 'Unknown error')}",
424
+ file=sys.stderr,
425
+ )
426
+ return
427
+
428
+ elements = result.get("elements", [])
429
+ language = result.get("language", "Unknown")
430
+
431
+ print("Code Element Extraction Results")
432
+ print("=" * 31)
433
+ print(f"File: {result.get('file_path', 'Unknown')}")
434
+ print(f"Language: {language}")
435
+ print(f"Elements Found: {len(elements)}")
436
+
437
+ if elements:
438
+ print("\nElements:")
439
+ for element in elements:
440
+ name = element.get("name", "Unknown")
441
+ elem_type = element.get("type", "unknown")
442
+ start_line = element.get("start_line", 0)
443
+ print(f" {elem_type}: {name} (line {start_line})")
444
+
445
+
446
+ def format_query_output(result: dict[str, Any], output_format: str) -> None:
447
+ """Format and display query results."""
448
+ if not result.get("success", False):
449
+ print(f"Query failed: {result.get('error', 'Unknown error')}", file=sys.stderr)
450
+ return
451
+
452
+ query_name = result.get("query_name", "Unknown")
453
+ matches = result.get("results", [])
454
+ language = result.get("language", "Unknown")
455
+
456
+ print("Query Execution Results")
457
+ print("=" * 23)
458
+ print(f"File: {result.get('file_path', 'Unknown')}")
459
+ print(f"Language: {language}")
460
+ print(f"Query: {query_name}")
461
+ print(f"Matches: {len(matches)}")
462
+
463
+ if matches:
464
+ print("\nMatches:")
465
+ for i, match in enumerate(matches, 1):
466
+ start_line = match.get("start_line", 0)
467
+ content = match.get("content", "").strip()
468
+ if len(content) > 50:
469
+ content = content[:47] + "..."
470
+ print(f" {i}. Line {start_line}: {content}")
471
+
472
+
473
+ def format_validation_output(result: dict[str, Any], output_format: str) -> None:
474
+ """Format and display validation results."""
475
+ valid = result.get("valid", False)
476
+ exists = result.get("exists", False)
477
+ readable = result.get("readable", False)
478
+ language = result.get("language")
479
+ supported = result.get("supported", False)
480
+ errors = result.get("errors", [])
481
+
482
+ print("File Validation Results")
483
+ print("=" * 23)
484
+ print(f"Valid: {'✓' if valid else '✗'}")
485
+ print(f"Exists: {'✓' if exists else '✗'}")
486
+ print(f"Readable: {'✓' if readable else '✗'}")
487
+ print(f"Language: {language or 'Unknown'}")
488
+ print(f"Supported: {'✓' if supported else '✗'}")
489
+
490
+ if errors:
491
+ print("\nErrors:")
492
+ for error in errors:
493
+ print(f" - {error}")
494
+
495
+
496
+ def main() -> int:
497
+ """Main CLI entry point."""
498
+ parser = create_parser()
499
+ args = parser.parse_args()
500
+
501
+ # Configure logging based on verbosity
502
+ if args.quiet:
503
+ logging.getLogger().setLevel(logging.ERROR)
504
+ elif args.verbose:
505
+ logging.getLogger().setLevel(logging.DEBUG)
506
+
507
+ # Handle commands
508
+ if args.command == "analyze":
509
+ return handle_analyze_command(args)
510
+ elif args.command == "extract":
511
+ return handle_extract_command(args)
512
+ elif args.command == "query":
513
+ return handle_query_command(args)
514
+ elif args.command == "validate":
515
+ return handle_validate_command(args)
516
+ elif args.command == "languages":
517
+ return handle_languages_command(args)
518
+ elif args.command == "info":
519
+ return handle_info_command(args)
520
+ elif args.command == "queries":
521
+ return handle_queries_command(args)
522
+ else:
523
+ parser.print_help()
524
+ return 1
525
+
526
+
527
+ if __name__ == "__main__":
528
+ sys.exit(main())