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.
Files changed (149) hide show
  1. tree_sitter_analyzer/__init__.py +132 -0
  2. tree_sitter_analyzer/__main__.py +11 -0
  3. tree_sitter_analyzer/api.py +853 -0
  4. tree_sitter_analyzer/cli/__init__.py +39 -0
  5. tree_sitter_analyzer/cli/__main__.py +12 -0
  6. tree_sitter_analyzer/cli/argument_validator.py +89 -0
  7. tree_sitter_analyzer/cli/commands/__init__.py +26 -0
  8. tree_sitter_analyzer/cli/commands/advanced_command.py +226 -0
  9. tree_sitter_analyzer/cli/commands/base_command.py +181 -0
  10. tree_sitter_analyzer/cli/commands/default_command.py +18 -0
  11. tree_sitter_analyzer/cli/commands/find_and_grep_cli.py +188 -0
  12. tree_sitter_analyzer/cli/commands/list_files_cli.py +133 -0
  13. tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -0
  14. tree_sitter_analyzer/cli/commands/query_command.py +109 -0
  15. tree_sitter_analyzer/cli/commands/search_content_cli.py +161 -0
  16. tree_sitter_analyzer/cli/commands/structure_command.py +156 -0
  17. tree_sitter_analyzer/cli/commands/summary_command.py +116 -0
  18. tree_sitter_analyzer/cli/commands/table_command.py +414 -0
  19. tree_sitter_analyzer/cli/info_commands.py +124 -0
  20. tree_sitter_analyzer/cli_main.py +472 -0
  21. tree_sitter_analyzer/constants.py +85 -0
  22. tree_sitter_analyzer/core/__init__.py +15 -0
  23. tree_sitter_analyzer/core/analysis_engine.py +580 -0
  24. tree_sitter_analyzer/core/cache_service.py +333 -0
  25. tree_sitter_analyzer/core/engine.py +585 -0
  26. tree_sitter_analyzer/core/parser.py +293 -0
  27. tree_sitter_analyzer/core/query.py +605 -0
  28. tree_sitter_analyzer/core/query_filter.py +200 -0
  29. tree_sitter_analyzer/core/query_service.py +340 -0
  30. tree_sitter_analyzer/encoding_utils.py +530 -0
  31. tree_sitter_analyzer/exceptions.py +747 -0
  32. tree_sitter_analyzer/file_handler.py +246 -0
  33. tree_sitter_analyzer/formatters/__init__.py +1 -0
  34. tree_sitter_analyzer/formatters/base_formatter.py +201 -0
  35. tree_sitter_analyzer/formatters/csharp_formatter.py +367 -0
  36. tree_sitter_analyzer/formatters/formatter_config.py +197 -0
  37. tree_sitter_analyzer/formatters/formatter_factory.py +84 -0
  38. tree_sitter_analyzer/formatters/formatter_registry.py +377 -0
  39. tree_sitter_analyzer/formatters/formatter_selector.py +96 -0
  40. tree_sitter_analyzer/formatters/go_formatter.py +368 -0
  41. tree_sitter_analyzer/formatters/html_formatter.py +498 -0
  42. tree_sitter_analyzer/formatters/java_formatter.py +423 -0
  43. tree_sitter_analyzer/formatters/javascript_formatter.py +611 -0
  44. tree_sitter_analyzer/formatters/kotlin_formatter.py +268 -0
  45. tree_sitter_analyzer/formatters/language_formatter_factory.py +123 -0
  46. tree_sitter_analyzer/formatters/legacy_formatter_adapters.py +228 -0
  47. tree_sitter_analyzer/formatters/markdown_formatter.py +725 -0
  48. tree_sitter_analyzer/formatters/php_formatter.py +301 -0
  49. tree_sitter_analyzer/formatters/python_formatter.py +830 -0
  50. tree_sitter_analyzer/formatters/ruby_formatter.py +278 -0
  51. tree_sitter_analyzer/formatters/rust_formatter.py +233 -0
  52. tree_sitter_analyzer/formatters/sql_formatter_wrapper.py +689 -0
  53. tree_sitter_analyzer/formatters/sql_formatters.py +536 -0
  54. tree_sitter_analyzer/formatters/typescript_formatter.py +543 -0
  55. tree_sitter_analyzer/formatters/yaml_formatter.py +462 -0
  56. tree_sitter_analyzer/interfaces/__init__.py +9 -0
  57. tree_sitter_analyzer/interfaces/cli.py +535 -0
  58. tree_sitter_analyzer/interfaces/cli_adapter.py +359 -0
  59. tree_sitter_analyzer/interfaces/mcp_adapter.py +224 -0
  60. tree_sitter_analyzer/interfaces/mcp_server.py +428 -0
  61. tree_sitter_analyzer/language_detector.py +553 -0
  62. tree_sitter_analyzer/language_loader.py +271 -0
  63. tree_sitter_analyzer/languages/__init__.py +10 -0
  64. tree_sitter_analyzer/languages/csharp_plugin.py +1076 -0
  65. tree_sitter_analyzer/languages/css_plugin.py +449 -0
  66. tree_sitter_analyzer/languages/go_plugin.py +836 -0
  67. tree_sitter_analyzer/languages/html_plugin.py +496 -0
  68. tree_sitter_analyzer/languages/java_plugin.py +1299 -0
  69. tree_sitter_analyzer/languages/javascript_plugin.py +1622 -0
  70. tree_sitter_analyzer/languages/kotlin_plugin.py +656 -0
  71. tree_sitter_analyzer/languages/markdown_plugin.py +1928 -0
  72. tree_sitter_analyzer/languages/php_plugin.py +862 -0
  73. tree_sitter_analyzer/languages/python_plugin.py +1636 -0
  74. tree_sitter_analyzer/languages/ruby_plugin.py +757 -0
  75. tree_sitter_analyzer/languages/rust_plugin.py +673 -0
  76. tree_sitter_analyzer/languages/sql_plugin.py +2444 -0
  77. tree_sitter_analyzer/languages/typescript_plugin.py +1892 -0
  78. tree_sitter_analyzer/languages/yaml_plugin.py +695 -0
  79. tree_sitter_analyzer/legacy_table_formatter.py +860 -0
  80. tree_sitter_analyzer/mcp/__init__.py +34 -0
  81. tree_sitter_analyzer/mcp/resources/__init__.py +43 -0
  82. tree_sitter_analyzer/mcp/resources/code_file_resource.py +208 -0
  83. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +586 -0
  84. tree_sitter_analyzer/mcp/server.py +869 -0
  85. tree_sitter_analyzer/mcp/tools/__init__.py +28 -0
  86. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +779 -0
  87. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +291 -0
  88. tree_sitter_analyzer/mcp/tools/base_tool.py +139 -0
  89. tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +816 -0
  90. tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +686 -0
  91. tree_sitter_analyzer/mcp/tools/list_files_tool.py +413 -0
  92. tree_sitter_analyzer/mcp/tools/output_format_validator.py +148 -0
  93. tree_sitter_analyzer/mcp/tools/query_tool.py +443 -0
  94. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +464 -0
  95. tree_sitter_analyzer/mcp/tools/search_content_tool.py +836 -0
  96. tree_sitter_analyzer/mcp/tools/table_format_tool.py +572 -0
  97. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +653 -0
  98. tree_sitter_analyzer/mcp/utils/__init__.py +113 -0
  99. tree_sitter_analyzer/mcp/utils/error_handler.py +569 -0
  100. tree_sitter_analyzer/mcp/utils/file_output_factory.py +217 -0
  101. tree_sitter_analyzer/mcp/utils/file_output_manager.py +322 -0
  102. tree_sitter_analyzer/mcp/utils/gitignore_detector.py +358 -0
  103. tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -0
  104. tree_sitter_analyzer/mcp/utils/search_cache.py +343 -0
  105. tree_sitter_analyzer/models.py +840 -0
  106. tree_sitter_analyzer/mypy_current_errors.txt +2 -0
  107. tree_sitter_analyzer/output_manager.py +255 -0
  108. tree_sitter_analyzer/platform_compat/__init__.py +3 -0
  109. tree_sitter_analyzer/platform_compat/adapter.py +324 -0
  110. tree_sitter_analyzer/platform_compat/compare.py +224 -0
  111. tree_sitter_analyzer/platform_compat/detector.py +67 -0
  112. tree_sitter_analyzer/platform_compat/fixtures.py +228 -0
  113. tree_sitter_analyzer/platform_compat/profiles.py +217 -0
  114. tree_sitter_analyzer/platform_compat/record.py +55 -0
  115. tree_sitter_analyzer/platform_compat/recorder.py +155 -0
  116. tree_sitter_analyzer/platform_compat/report.py +92 -0
  117. tree_sitter_analyzer/plugins/__init__.py +280 -0
  118. tree_sitter_analyzer/plugins/base.py +647 -0
  119. tree_sitter_analyzer/plugins/manager.py +384 -0
  120. tree_sitter_analyzer/project_detector.py +328 -0
  121. tree_sitter_analyzer/queries/__init__.py +27 -0
  122. tree_sitter_analyzer/queries/csharp.py +216 -0
  123. tree_sitter_analyzer/queries/css.py +615 -0
  124. tree_sitter_analyzer/queries/go.py +275 -0
  125. tree_sitter_analyzer/queries/html.py +543 -0
  126. tree_sitter_analyzer/queries/java.py +402 -0
  127. tree_sitter_analyzer/queries/javascript.py +724 -0
  128. tree_sitter_analyzer/queries/kotlin.py +192 -0
  129. tree_sitter_analyzer/queries/markdown.py +258 -0
  130. tree_sitter_analyzer/queries/php.py +95 -0
  131. tree_sitter_analyzer/queries/python.py +859 -0
  132. tree_sitter_analyzer/queries/ruby.py +92 -0
  133. tree_sitter_analyzer/queries/rust.py +223 -0
  134. tree_sitter_analyzer/queries/sql.py +555 -0
  135. tree_sitter_analyzer/queries/typescript.py +871 -0
  136. tree_sitter_analyzer/queries/yaml.py +236 -0
  137. tree_sitter_analyzer/query_loader.py +272 -0
  138. tree_sitter_analyzer/security/__init__.py +22 -0
  139. tree_sitter_analyzer/security/boundary_manager.py +277 -0
  140. tree_sitter_analyzer/security/regex_checker.py +297 -0
  141. tree_sitter_analyzer/security/validator.py +599 -0
  142. tree_sitter_analyzer/table_formatter.py +782 -0
  143. tree_sitter_analyzer/utils/__init__.py +53 -0
  144. tree_sitter_analyzer/utils/logging.py +433 -0
  145. tree_sitter_analyzer/utils/tree_sitter_compat.py +289 -0
  146. tree_sitter_analyzer-1.9.17.1.dist-info/METADATA +485 -0
  147. tree_sitter_analyzer-1.9.17.1.dist-info/RECORD +149 -0
  148. tree_sitter_analyzer-1.9.17.1.dist-info/WHEEL +4 -0
  149. tree_sitter_analyzer-1.9.17.1.dist-info/entry_points.txt +25 -0
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Standalone CLI for find_and_grep (fd → ripgrep composition)
4
+
5
+ Maps CLI flags to the MCP FindAndGrepTool and prints JSON/text.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import asyncio
12
+ import sys
13
+ from typing import Any
14
+
15
+ from ...mcp.tools.find_and_grep_tool import FindAndGrepTool
16
+ from ...output_manager import output_data, output_error, set_output_mode
17
+ from ...project_detector import detect_project_root
18
+
19
+
20
+ def _build_parser() -> argparse.ArgumentParser:
21
+ parser = argparse.ArgumentParser(
22
+ description="Two-stage search: fd for files, then ripgrep for content.",
23
+ )
24
+
25
+ # Required
26
+ parser.add_argument("--roots", nargs="+", required=True, help="Search roots")
27
+ parser.add_argument("--query", required=True, help="Content query")
28
+
29
+ # Output
30
+ parser.add_argument(
31
+ "--output-format",
32
+ choices=["json", "text"],
33
+ default="json",
34
+ help="Output format (default: json)",
35
+ )
36
+ parser.add_argument(
37
+ "--quiet",
38
+ action="store_true",
39
+ help="Suppress non-essential output",
40
+ )
41
+
42
+ # fd options (subset mirrors ListFiles)
43
+ parser.add_argument("--pattern")
44
+ parser.add_argument("--glob", action="store_true")
45
+ parser.add_argument("--types", nargs="+")
46
+ parser.add_argument("--extensions", nargs="+")
47
+ parser.add_argument("--exclude", nargs="+")
48
+ parser.add_argument("--depth", type=int)
49
+ parser.add_argument("--follow-symlinks", action="store_true")
50
+ parser.add_argument("--hidden", action="store_true")
51
+ parser.add_argument("--no-ignore", action="store_true")
52
+ parser.add_argument("--size", nargs="+")
53
+ parser.add_argument("--changed-within")
54
+ parser.add_argument("--changed-before")
55
+ parser.add_argument("--full-path-match", action="store_true")
56
+ parser.add_argument("--file-limit", type=int)
57
+ parser.add_argument("--sort", choices=["path", "mtime", "size"])
58
+
59
+ # rg options (subset mirrors SearchContent)
60
+ parser.add_argument(
61
+ "--case", choices=["smart", "insensitive", "sensitive"], default="smart"
62
+ )
63
+ parser.add_argument("--fixed-strings", action="store_true")
64
+ parser.add_argument("--word", action="store_true")
65
+ parser.add_argument("--multiline", action="store_true")
66
+ parser.add_argument("--include-globs", nargs="+")
67
+ parser.add_argument("--exclude-globs", nargs="+")
68
+ parser.add_argument("--max-filesize")
69
+ parser.add_argument("--context-before", type=int)
70
+ parser.add_argument("--context-after", type=int)
71
+ parser.add_argument("--encoding")
72
+ parser.add_argument("--max-count", type=int)
73
+ parser.add_argument("--timeout-ms", type=int)
74
+ parser.add_argument("--count-only-matches", action="store_true")
75
+ parser.add_argument("--summary-only", action="store_true")
76
+ parser.add_argument("--optimize-paths", action="store_true")
77
+ parser.add_argument("--group-by-file", action="store_true")
78
+ parser.add_argument("--total-only", action="store_true")
79
+
80
+ # project root
81
+ parser.add_argument(
82
+ "--project-root",
83
+ help="Project root directory for security boundary (auto-detected if omitted)",
84
+ )
85
+
86
+ return parser
87
+
88
+
89
+ async def _run(args: argparse.Namespace) -> int:
90
+ set_output_mode(quiet=bool(args.quiet), json_output=(args.output_format == "json"))
91
+
92
+ project_root = detect_project_root(None, args.project_root)
93
+ tool = FindAndGrepTool(project_root)
94
+
95
+ payload: dict[str, Any] = {
96
+ "roots": list(args.roots),
97
+ "query": args.query,
98
+ }
99
+
100
+ # fd stage mappings
101
+ if args.pattern:
102
+ payload["pattern"] = args.pattern
103
+ if args.glob:
104
+ payload["glob"] = True
105
+ if args.types:
106
+ payload["types"] = args.types
107
+ if args.extensions:
108
+ payload["extensions"] = args.extensions
109
+ if args.exclude:
110
+ payload["exclude"] = args.exclude
111
+ if args.depth is not None:
112
+ payload["depth"] = int(args.depth)
113
+ if args.follow_symlinks:
114
+ payload["follow_symlinks"] = True
115
+ if args.hidden:
116
+ payload["hidden"] = True
117
+ if args.no_ignore:
118
+ payload["no_ignore"] = True
119
+ if args.size:
120
+ payload["size"] = args.size
121
+ if args.changed_within:
122
+ payload["changed_within"] = args.changed_within
123
+ if args.changed_before:
124
+ payload["changed_before"] = args.changed_before
125
+ if args.full_path_match:
126
+ payload["full_path_match"] = True
127
+ if args.file_limit is not None:
128
+ payload["file_limit"] = int(args.file_limit)
129
+ if args.sort:
130
+ payload["sort"] = args.sort
131
+
132
+ # rg stage mappings
133
+ if args.case:
134
+ payload["case"] = args.case
135
+ if args.fixed_strings:
136
+ payload["fixed_strings"] = True
137
+ if args.word:
138
+ payload["word"] = True
139
+ if args.multiline:
140
+ payload["multiline"] = True
141
+ if args.include_globs:
142
+ payload["include_globs"] = args.include_globs
143
+ if args.exclude_globs:
144
+ payload["exclude_globs"] = args.exclude_globs
145
+ if args.max_filesize:
146
+ payload["max_filesize"] = args.max_filesize
147
+ if args.context_before is not None:
148
+ payload["context_before"] = int(args.context_before)
149
+ if args.context_after is not None:
150
+ payload["context_after"] = int(args.context_after)
151
+ if args.encoding:
152
+ payload["encoding"] = args.encoding
153
+ if args.max_count is not None:
154
+ payload["max_count"] = int(args.max_count)
155
+ if args.timeout_ms is not None:
156
+ payload["timeout_ms"] = int(args.timeout_ms)
157
+ if args.count_only_matches:
158
+ payload["count_only_matches"] = True
159
+ if args.summary_only:
160
+ payload["summary_only"] = True
161
+ if args.optimize_paths:
162
+ payload["optimize_paths"] = True
163
+ if args.group_by_file:
164
+ payload["group_by_file"] = True
165
+ if args.total_only:
166
+ payload["total_only"] = True
167
+
168
+ try:
169
+ result = await tool.execute(payload)
170
+ output_data(result, args.output_format)
171
+ return 0 if (isinstance(result, dict) or isinstance(result, int)) else 0
172
+ except Exception as e:
173
+ output_error(str(e))
174
+ return 1
175
+
176
+
177
+ def main() -> None:
178
+ parser = _build_parser()
179
+ args = parser.parse_args()
180
+ try:
181
+ rc = asyncio.run(_run(args))
182
+ except KeyboardInterrupt:
183
+ rc = 1
184
+ sys.exit(rc)
185
+
186
+
187
+ if __name__ == "__main__":
188
+ main()
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Standalone CLI for list_files (fd wrapper)
4
+
5
+ Maps CLI flags to the MCP ListFilesTool and prints JSON/text via OutputManager.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import asyncio
12
+ import sys
13
+ from typing import Any
14
+
15
+ from ...mcp.tools.list_files_tool import ListFilesTool
16
+ from ...output_manager import output_data, output_error, set_output_mode
17
+ from ...project_detector import detect_project_root
18
+
19
+
20
+ def _build_parser() -> argparse.ArgumentParser:
21
+ parser = argparse.ArgumentParser(
22
+ description="List files and directories using fd via MCP wrapper.",
23
+ )
24
+
25
+ # Roots
26
+ parser.add_argument(
27
+ "roots",
28
+ nargs="+",
29
+ help="One or more root directories to search in",
30
+ )
31
+
32
+ # Output
33
+ parser.add_argument(
34
+ "--output-format",
35
+ choices=["json", "text"],
36
+ default="json",
37
+ help="Output format (default: json)",
38
+ )
39
+ parser.add_argument(
40
+ "--quiet",
41
+ action="store_true",
42
+ help="Suppress non-essential output",
43
+ )
44
+
45
+ # fd options
46
+ parser.add_argument("--pattern")
47
+ parser.add_argument("--glob", action="store_true")
48
+ parser.add_argument("--types", nargs="+")
49
+ parser.add_argument("--extensions", nargs="+")
50
+ parser.add_argument("--exclude", nargs="+")
51
+ parser.add_argument("--depth", type=int)
52
+ parser.add_argument("--follow-symlinks", action="store_true")
53
+ parser.add_argument("--hidden", action="store_true")
54
+ parser.add_argument("--no-ignore", action="store_true")
55
+ parser.add_argument("--size", nargs="+")
56
+ parser.add_argument("--changed-within")
57
+ parser.add_argument("--changed-before")
58
+ parser.add_argument("--full-path-match", action="store_true")
59
+ parser.add_argument("--limit", type=int)
60
+ parser.add_argument("--count-only", action="store_true")
61
+
62
+ # project root
63
+ parser.add_argument(
64
+ "--project-root",
65
+ help="Project root directory for security boundary (auto-detected if omitted)",
66
+ )
67
+
68
+ return parser
69
+
70
+
71
+ async def _run(args: argparse.Namespace) -> int:
72
+ set_output_mode(quiet=bool(args.quiet), json_output=(args.output_format == "json"))
73
+
74
+ project_root = detect_project_root(None, args.project_root)
75
+ tool = ListFilesTool(project_root)
76
+
77
+ payload: dict[str, Any] = {
78
+ "roots": list(args.roots),
79
+ }
80
+
81
+ # Optional mappings
82
+ if args.pattern:
83
+ payload["pattern"] = args.pattern
84
+ if args.glob:
85
+ payload["glob"] = True
86
+ if args.types:
87
+ payload["types"] = args.types
88
+ if args.extensions:
89
+ payload["extensions"] = args.extensions
90
+ if args.exclude:
91
+ payload["exclude"] = args.exclude
92
+ if args.depth is not None:
93
+ payload["depth"] = int(args.depth)
94
+ if args.follow_symlinks:
95
+ payload["follow_symlinks"] = True
96
+ if args.hidden:
97
+ payload["hidden"] = True
98
+ if args.no_ignore:
99
+ payload["no_ignore"] = True
100
+ if args.size:
101
+ payload["size"] = args.size
102
+ if args.changed_within:
103
+ payload["changed_within"] = args.changed_within
104
+ if args.changed_before:
105
+ payload["changed_before"] = args.changed_before
106
+ if args.full_path_match:
107
+ payload["full_path_match"] = True
108
+ if args.limit is not None:
109
+ payload["limit"] = int(args.limit)
110
+ if args.count_only:
111
+ payload["count_only"] = True
112
+
113
+ try:
114
+ result = await tool.execute(payload)
115
+ output_data(result, args.output_format)
116
+ return 0 if (isinstance(result, dict) and result.get("success", True)) else 0
117
+ except Exception as e:
118
+ output_error(str(e))
119
+ return 1
120
+
121
+
122
+ def main() -> None:
123
+ parser = _build_parser()
124
+ args = parser.parse_args()
125
+ try:
126
+ rc = asyncio.run(_run(args))
127
+ except KeyboardInterrupt:
128
+ rc = 1
129
+ sys.exit(rc)
130
+
131
+
132
+ if __name__ == "__main__":
133
+ main()
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Partial Read Command
4
+
5
+ Handles partial file reading functionality, extracting specified line ranges.
6
+ """
7
+
8
+ from typing import TYPE_CHECKING, Any
9
+
10
+ from ...file_handler import read_file_partial
11
+ from ...output_manager import output_data, output_json, output_section
12
+ from .base_command import BaseCommand
13
+
14
+ if TYPE_CHECKING:
15
+ pass
16
+
17
+
18
+ class PartialReadCommand(BaseCommand):
19
+ """Command for reading partial file content by line range."""
20
+
21
+ def __init__(self, args: Any) -> None:
22
+ """Initialize with arguments but skip base class analysis engine setup."""
23
+ self.args = args
24
+ # Don't call super().__init__() to avoid unnecessary analysis engine setup
25
+
26
+ def validate_file(self) -> bool:
27
+ """Validate input file exists and is accessible."""
28
+ if not hasattr(self.args, "file_path") or not self.args.file_path:
29
+ from ...output_manager import output_error
30
+
31
+ output_error("File path not specified.")
32
+ return False
33
+
34
+ from pathlib import Path
35
+
36
+ if not Path(self.args.file_path).exists():
37
+ from ...output_manager import output_error
38
+
39
+ output_error(f"File not found: {self.args.file_path}")
40
+ return False
41
+
42
+ return True
43
+
44
+ def execute(self) -> int:
45
+ """
46
+ Execute partial read command.
47
+
48
+ Returns:
49
+ int: Exit code (0 for success, 1 for failure)
50
+ """
51
+ # Validate inputs
52
+ if not self.validate_file():
53
+ return 1
54
+
55
+ # Validate partial read arguments
56
+ if not self.args.start_line:
57
+ from ...output_manager import output_error
58
+
59
+ output_error("--start-line is required")
60
+ return 1
61
+
62
+ if self.args.start_line < 1:
63
+ from ...output_manager import output_error
64
+
65
+ output_error("--start-line must be 1 or greater")
66
+ return 1
67
+
68
+ if self.args.end_line and self.args.end_line < self.args.start_line:
69
+ from ...output_manager import output_error
70
+
71
+ output_error("--end-line must be greater than or equal to --start-line")
72
+ return 1
73
+
74
+ # Read partial content
75
+ try:
76
+ partial_content = read_file_partial(
77
+ self.args.file_path,
78
+ start_line=self.args.start_line,
79
+ end_line=getattr(self.args, "end_line", None),
80
+ start_column=getattr(self.args, "start_column", None),
81
+ end_column=getattr(self.args, "end_column", None),
82
+ )
83
+
84
+ if partial_content is None:
85
+ from ...output_manager import output_error
86
+
87
+ output_error("Failed to read file partially")
88
+ return 1
89
+
90
+ # Output the result
91
+ self._output_partial_content(partial_content)
92
+ return 0
93
+
94
+ except Exception as e:
95
+ from ...output_manager import output_error
96
+
97
+ output_error(f"Failed to read file partially: {e}")
98
+ return 1
99
+
100
+ def _output_partial_content(self, content: str) -> None:
101
+ """Output the partial content in the specified format."""
102
+ # Build result data
103
+ result_data = {
104
+ "file_path": self.args.file_path,
105
+ "range": {
106
+ "start_line": self.args.start_line,
107
+ "end_line": getattr(self.args, "end_line", None),
108
+ "start_column": getattr(self.args, "start_column", None),
109
+ "end_column": getattr(self.args, "end_column", None),
110
+ },
111
+ "content": content,
112
+ "content_length": len(content),
113
+ }
114
+
115
+ # Build range info for header
116
+ range_info = f"Line {self.args.start_line}"
117
+ if hasattr(self.args, "end_line") and self.args.end_line:
118
+ range_info += f"-{self.args.end_line}"
119
+
120
+ # Output format selection
121
+ output_format = getattr(self.args, "output_format", "text")
122
+
123
+ if output_format == "json":
124
+ # Pure JSON output
125
+ output_json(result_data)
126
+ else:
127
+ # Human-readable format with header
128
+ output_section("Partial Read Result")
129
+ output_data(f"File: {self.args.file_path}")
130
+ output_data(f"Range: {range_info}")
131
+ output_data(f"Characters read: {len(content)}")
132
+ output_data("") # Empty line for separation
133
+
134
+ # Output the actual content
135
+ print(content, end="") # Use print to avoid extra formatting
136
+
137
+ async def execute_async(self, language: str) -> int:
138
+ """Not used for partial read command."""
139
+ return self.execute()
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Query Command
4
+
5
+ Handles query execution functionality.
6
+ """
7
+
8
+ from typing import Any
9
+
10
+ from ...core.query_service import QueryService
11
+ from ...output_manager import output_data, output_error, output_info, output_json
12
+ from .base_command import BaseCommand
13
+
14
+
15
+ class QueryCommand(BaseCommand):
16
+ """Command for executing queries."""
17
+
18
+ def __init__(self, args: Any) -> None:
19
+ """Initialize the query command with QueryService."""
20
+ super().__init__(args)
21
+ self.query_service = QueryService()
22
+
23
+ async def execute_query(
24
+ self, language: str, query: str, query_name: str = "custom"
25
+ ) -> list[dict] | None:
26
+ """Execute a specific tree-sitter query using QueryService."""
27
+ try:
28
+ # Get filter expression if provided
29
+ filter_expression = getattr(self.args, "filter", None)
30
+
31
+ if query_name != "custom":
32
+ # Use predefined query key
33
+ results = await self.query_service.execute_query(
34
+ self.args.file_path,
35
+ language,
36
+ query_key=query_name,
37
+ filter_expression=filter_expression,
38
+ )
39
+ else:
40
+ # Use custom query string
41
+ results = await self.query_service.execute_query(
42
+ self.args.file_path,
43
+ language,
44
+ query_string=query,
45
+ filter_expression=filter_expression,
46
+ )
47
+
48
+ return results
49
+
50
+ except Exception as e:
51
+ output_error(f"Query execution failed: {e}")
52
+ return None
53
+
54
+ async def execute_async(self, language: str) -> int:
55
+ # Get the query to execute
56
+ query_to_execute = None
57
+
58
+ if hasattr(self.args, "query_key") and self.args.query_key:
59
+ # Sanitize query key input
60
+ sanitized_query_key = self.security_validator.sanitize_input(
61
+ self.args.query_key, max_length=100
62
+ )
63
+ # Check if query exists
64
+ available_queries = self.query_service.get_available_queries(language)
65
+ if sanitized_query_key not in available_queries:
66
+ output_error(
67
+ f"Query '{sanitized_query_key}' not found for language '{language}'"
68
+ )
69
+ return 1
70
+ # Store query name - QueryService will resolve the query string
71
+ query_to_execute = sanitized_query_key # This is actually the query key now
72
+ query_name = sanitized_query_key
73
+ elif hasattr(self.args, "query_string") and self.args.query_string:
74
+ # Security check for query string (potential regex patterns)
75
+ is_safe, error_msg = self.security_validator.regex_checker.validate_pattern(
76
+ self.args.query_string
77
+ )
78
+ if not is_safe:
79
+ output_error(f"Unsafe query pattern: {error_msg}")
80
+ return 1
81
+ query_to_execute = self.args.query_string
82
+ query_name = "custom"
83
+
84
+ if not query_to_execute:
85
+ output_error("No query specified.")
86
+ return 1
87
+
88
+ # Execute specific query
89
+ results = await self.execute_query(language, query_to_execute, query_name)
90
+ if results is None:
91
+ return 1
92
+
93
+ # Output results
94
+ if results:
95
+ if self.args.output_format == "json":
96
+ output_json(results)
97
+ else:
98
+ for i, query_result in enumerate(results, 1):
99
+ output_data(
100
+ f"\n{i}. {query_result['capture_name']} ({query_result['node_type']})"
101
+ )
102
+ output_data(
103
+ f" Position: Line {query_result['start_line']}-{query_result['end_line']}"
104
+ )
105
+ output_data(f" Content:\n{query_result['content']}")
106
+ else:
107
+ output_info("\nINFO: No results found matching the query.")
108
+
109
+ return 0