agentcrew-ai 0.8.12__py3-none-any.whl → 0.9.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.
- AgentCrew/__init__.py +1 -1
- AgentCrew/app.py +34 -633
- AgentCrew/main.py +55 -3
- AgentCrew/main_docker.py +1 -30
- AgentCrew/modules/agents/local_agent.py +26 -1
- AgentCrew/modules/chat/message/command_processor.py +33 -8
- AgentCrew/modules/chat/message/handler.py +5 -1
- AgentCrew/modules/code_analysis/__init__.py +8 -0
- AgentCrew/modules/code_analysis/parsers/__init__.py +67 -0
- AgentCrew/modules/code_analysis/parsers/base.py +93 -0
- AgentCrew/modules/code_analysis/parsers/cpp_parser.py +127 -0
- AgentCrew/modules/code_analysis/parsers/csharp_parser.py +162 -0
- AgentCrew/modules/code_analysis/parsers/generic_parser.py +63 -0
- AgentCrew/modules/code_analysis/parsers/go_parser.py +154 -0
- AgentCrew/modules/code_analysis/parsers/java_parser.py +103 -0
- AgentCrew/modules/code_analysis/parsers/javascript_parser.py +268 -0
- AgentCrew/modules/code_analysis/parsers/kotlin_parser.py +84 -0
- AgentCrew/modules/code_analysis/parsers/php_parser.py +107 -0
- AgentCrew/modules/code_analysis/parsers/python_parser.py +60 -0
- AgentCrew/modules/code_analysis/parsers/ruby_parser.py +46 -0
- AgentCrew/modules/code_analysis/parsers/rust_parser.py +72 -0
- AgentCrew/modules/code_analysis/service.py +231 -897
- AgentCrew/modules/command_execution/constants.py +2 -2
- AgentCrew/modules/console/completers.py +1 -1
- AgentCrew/modules/console/confirmation_handler.py +4 -4
- AgentCrew/modules/console/console_ui.py +17 -3
- AgentCrew/modules/console/conversation_browser/__init__.py +9 -0
- AgentCrew/modules/console/conversation_browser/browser.py +84 -0
- AgentCrew/modules/console/conversation_browser/browser_input_handler.py +279 -0
- AgentCrew/modules/console/conversation_browser/browser_ui.py +643 -0
- AgentCrew/modules/console/conversation_handler.py +34 -1
- AgentCrew/modules/console/diff_display.py +22 -51
- AgentCrew/modules/console/display_handlers.py +142 -26
- AgentCrew/modules/console/tool_display.py +4 -6
- AgentCrew/modules/file_editing/service.py +8 -8
- AgentCrew/modules/file_editing/tool.py +65 -67
- AgentCrew/modules/gui/components/command_handler.py +137 -29
- AgentCrew/modules/gui/components/tool_handlers.py +0 -2
- AgentCrew/modules/gui/themes/README.md +30 -14
- AgentCrew/modules/gui/themes/__init__.py +2 -1
- AgentCrew/modules/gui/themes/atom_light.yaml +1287 -0
- AgentCrew/modules/gui/themes/catppuccin.yaml +1276 -0
- AgentCrew/modules/gui/themes/dracula.yaml +1262 -0
- AgentCrew/modules/gui/themes/nord.yaml +1267 -0
- AgentCrew/modules/gui/themes/saigontech.yaml +1268 -0
- AgentCrew/modules/gui/themes/style_provider.py +76 -264
- AgentCrew/modules/gui/themes/theme_loader.py +379 -0
- AgentCrew/modules/gui/themes/unicorn.yaml +1276 -0
- AgentCrew/modules/gui/widgets/configs/global_settings.py +3 -4
- AgentCrew/modules/gui/widgets/diff_widget.py +30 -61
- AgentCrew/modules/llm/constants.py +18 -9
- AgentCrew/modules/memory/context_persistent.py +1 -0
- AgentCrew/modules/memory/tool.py +1 -1
- AgentCrew/setup.py +470 -0
- {agentcrew_ai-0.8.12.dist-info → agentcrew_ai-0.9.0.dist-info}/METADATA +1 -1
- {agentcrew_ai-0.8.12.dist-info → agentcrew_ai-0.9.0.dist-info}/RECORD +60 -41
- {agentcrew_ai-0.8.12.dist-info → agentcrew_ai-0.9.0.dist-info}/WHEEL +1 -1
- AgentCrew/modules/gui/themes/atom_light.py +0 -1365
- AgentCrew/modules/gui/themes/catppuccin.py +0 -1404
- AgentCrew/modules/gui/themes/dracula.py +0 -1372
- AgentCrew/modules/gui/themes/nord.py +0 -1365
- AgentCrew/modules/gui/themes/saigontech.py +0 -1359
- AgentCrew/modules/gui/themes/unicorn.py +0 -1372
- {agentcrew_ai-0.8.12.dist-info → agentcrew_ai-0.9.0.dist-info}/entry_points.txt +0 -0
- {agentcrew_ai-0.8.12.dist-info → agentcrew_ai-0.9.0.dist-info}/licenses/LICENSE +0 -0
- {agentcrew_ai-0.8.12.dist-info → agentcrew_ai-0.9.0.dist-info}/top_level.txt +0 -0
AgentCrew/main.py
CHANGED
|
@@ -86,7 +86,7 @@ def check_and_update():
|
|
|
86
86
|
current_version = get_current_version()
|
|
87
87
|
|
|
88
88
|
click.echo(f"Current version: {current_version}\nChecking for updates...")
|
|
89
|
-
latest_version =
|
|
89
|
+
latest_version, release_notes = get_latest_release_info()
|
|
90
90
|
|
|
91
91
|
if not current_version or not latest_version:
|
|
92
92
|
click.echo("⚠️ Could not determine version information", err=True)
|
|
@@ -98,8 +98,20 @@ def check_and_update():
|
|
|
98
98
|
system = platform.system().lower()
|
|
99
99
|
|
|
100
100
|
if system == "linux" or system == "darwin":
|
|
101
|
+
click.echo("\n" + "=" * 60)
|
|
102
|
+
click.echo("🔄 New version available!")
|
|
103
|
+
click.echo("=" * 60)
|
|
104
|
+
|
|
105
|
+
if release_notes:
|
|
106
|
+
click.echo("\n📝 Release Notes:")
|
|
107
|
+
click.echo("-" * 40)
|
|
108
|
+
click.echo(release_notes)
|
|
109
|
+
click.echo("-" * 40 + "\n")
|
|
110
|
+
else:
|
|
111
|
+
click.echo("\n⚠️ Could not fetch release notes.")
|
|
112
|
+
|
|
101
113
|
if click.confirm(
|
|
102
|
-
"
|
|
114
|
+
"\nDo you want to update now?",
|
|
103
115
|
default=False,
|
|
104
116
|
):
|
|
105
117
|
click.echo("🔄 Starting update...")
|
|
@@ -109,7 +121,18 @@ def check_and_update():
|
|
|
109
121
|
click.echo("⏭️ Skipping update. Starting application...")
|
|
110
122
|
else:
|
|
111
123
|
command = "uv tool install --python=3.12 --reinstall agentcrew-ai[cpu]@latest --index https://download.pytorch.org/whl/cpu --index-strategy unsafe-best-match"
|
|
112
|
-
|
|
124
|
+
|
|
125
|
+
click.echo("\n" + "=" * 60)
|
|
126
|
+
click.echo("🔄 New version available!")
|
|
127
|
+
click.echo("=" * 60)
|
|
128
|
+
|
|
129
|
+
if release_notes:
|
|
130
|
+
click.echo("\n📝 Release Notes:")
|
|
131
|
+
click.echo("-" * 40)
|
|
132
|
+
click.echo(release_notes)
|
|
133
|
+
click.echo("-" * 40 + "\n")
|
|
134
|
+
|
|
135
|
+
click.echo(f"Run the following command to update:\n\n{command}")
|
|
113
136
|
else:
|
|
114
137
|
click.echo("✅ You are running the latest version")
|
|
115
138
|
|
|
@@ -157,6 +180,35 @@ def get_latest_github_version():
|
|
|
157
180
|
return None
|
|
158
181
|
|
|
159
182
|
|
|
183
|
+
def get_latest_release_info():
|
|
184
|
+
"""Get the latest release information including version and release notes from GitHub
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
tuple: (version, release_notes) where both can be None if not found.
|
|
188
|
+
"""
|
|
189
|
+
try:
|
|
190
|
+
api_url = (
|
|
191
|
+
"https://api.github.com/repos/saigontechnology/AgentCrew/releases/latest"
|
|
192
|
+
)
|
|
193
|
+
response = requests.get(api_url, timeout=10)
|
|
194
|
+
|
|
195
|
+
if response.status_code == 200:
|
|
196
|
+
release_data = response.json()
|
|
197
|
+
tag_name = release_data.get("tag_name", "").lstrip("v")
|
|
198
|
+
name = release_data.get("name", "")
|
|
199
|
+
body = release_data.get("body", "")
|
|
200
|
+
|
|
201
|
+
release_notes = None
|
|
202
|
+
if body:
|
|
203
|
+
release_notes = f"## {name or tag_name}\n\n{body}"
|
|
204
|
+
|
|
205
|
+
return tag_name, release_notes
|
|
206
|
+
|
|
207
|
+
return None, None
|
|
208
|
+
except Exception:
|
|
209
|
+
return None, None
|
|
210
|
+
|
|
211
|
+
|
|
160
212
|
def version_is_older(current: str, latest: str) -> bool:
|
|
161
213
|
"""
|
|
162
214
|
Compare two semantic version strings to check if current is older than latest.
|
AgentCrew/main_docker.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import click
|
|
2
2
|
import os
|
|
3
3
|
import sys
|
|
4
|
+
from AgentCrew.app import common_options
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
def _custom_unraisable_hook(unraisable):
|
|
@@ -48,36 +49,6 @@ def cli():
|
|
|
48
49
|
)
|
|
49
50
|
|
|
50
51
|
|
|
51
|
-
def common_options(func):
|
|
52
|
-
import functools
|
|
53
|
-
|
|
54
|
-
@click.option(
|
|
55
|
-
"--provider",
|
|
56
|
-
type=click.Choice(PROVIDER_LIST),
|
|
57
|
-
default=None,
|
|
58
|
-
help="LLM provider to use (claude, groq, openai, google, github_copilot, or deepinfra)",
|
|
59
|
-
)
|
|
60
|
-
@click.option(
|
|
61
|
-
"--agent-config", default=None, help="Path/URL to the agent configuration file."
|
|
62
|
-
)
|
|
63
|
-
@click.option(
|
|
64
|
-
"--mcp-config", default=None, help="Path to the mcp servers configuration file."
|
|
65
|
-
)
|
|
66
|
-
@click.option(
|
|
67
|
-
"--memory-llm",
|
|
68
|
-
type=click.Choice(
|
|
69
|
-
["claude", "groq", "openai", "google", "deepinfra", "github_copilot"]
|
|
70
|
-
),
|
|
71
|
-
default=None,
|
|
72
|
-
help="LLM Model use for analyzing and processing memory",
|
|
73
|
-
)
|
|
74
|
-
@functools.wraps(func)
|
|
75
|
-
def wrapper(*args, **kwargs):
|
|
76
|
-
return func(*args, **kwargs)
|
|
77
|
-
|
|
78
|
-
return wrapper
|
|
79
|
-
|
|
80
|
-
|
|
81
52
|
def cli_prod():
|
|
82
53
|
if sys.argv[1] == "--version":
|
|
83
54
|
click.echo(f"AgentCrew version: {get_current_version()}")
|
|
@@ -604,6 +604,21 @@ Whenever condition on `when` clause in a **Behavior** matches, tailor your respo
|
|
|
604
604
|
)
|
|
605
605
|
return adaptive_messages
|
|
606
606
|
|
|
607
|
+
def _get_directory_structure(self) -> str:
|
|
608
|
+
try:
|
|
609
|
+
cwd = os.getcwd()
|
|
610
|
+
entries = []
|
|
611
|
+
for entry in sorted(os.listdir(cwd)):
|
|
612
|
+
full_path = os.path.join(cwd, entry)
|
|
613
|
+
if os.path.isdir(full_path):
|
|
614
|
+
entries.append(f"{entry}/")
|
|
615
|
+
else:
|
|
616
|
+
entries.append(entry)
|
|
617
|
+
return "\n".join(entries) if entries else ""
|
|
618
|
+
except Exception as e:
|
|
619
|
+
logger.warning(f"Failed to get directory structure: {e}")
|
|
620
|
+
return ""
|
|
621
|
+
|
|
607
622
|
def _enhance_agent_context_messages(self, final_messages: List[Dict[str, Any]]):
|
|
608
623
|
last_user_index = next(
|
|
609
624
|
(
|
|
@@ -651,6 +666,16 @@ Whenever condition on `when` clause in a **Behavior** matches, tailor your respo
|
|
|
651
666
|
- Skip agent evaluation if user request is when...,[action]... related to adaptive behaviors call `adapt` tool instead.""",
|
|
652
667
|
},
|
|
653
668
|
)
|
|
669
|
+
if last_user_index == 0:
|
|
670
|
+
dir_structure = self._get_directory_structure()
|
|
671
|
+
if dir_structure:
|
|
672
|
+
adaptive_messages["content"].append(
|
|
673
|
+
{
|
|
674
|
+
"type": "text",
|
|
675
|
+
"text": f"cwd `{os.getcwd()}` structure:\n{dir_structure}",
|
|
676
|
+
}
|
|
677
|
+
)
|
|
678
|
+
|
|
654
679
|
if len(adaptive_messages["content"]) > 0:
|
|
655
680
|
final_messages.insert(last_user_index, adaptive_messages)
|
|
656
681
|
|
|
@@ -738,7 +763,7 @@ Whenever condition on `when` clause in a **Behavior** matches, tailor your respo
|
|
|
738
763
|
continue
|
|
739
764
|
|
|
740
765
|
if is_shrinkable and i < shrink_threshold:
|
|
741
|
-
msg["content"] = "[
|
|
766
|
+
msg["content"] = "[PRUNED]"
|
|
742
767
|
continue
|
|
743
768
|
|
|
744
769
|
# Check if content starts with [UNIQUE]
|
|
@@ -43,14 +43,8 @@ class CommandProcessor:
|
|
|
43
43
|
return CommandResult(handled=True, clear_flag=True)
|
|
44
44
|
elif user_input.lower().startswith("/copy"):
|
|
45
45
|
return await self._handle_copy_command(user_input)
|
|
46
|
-
elif user_input.lower()
|
|
47
|
-
self.
|
|
48
|
-
"debug_requested", self.message_handler.agent.clean_history
|
|
49
|
-
)
|
|
50
|
-
self.message_handler._notify(
|
|
51
|
-
"debug_requested", self.message_handler.streamline_messages
|
|
52
|
-
)
|
|
53
|
-
return CommandResult(handled=True, clear_flag=True)
|
|
46
|
+
elif user_input.lower().startswith("/debug"):
|
|
47
|
+
return self._handle_debug_command(user_input)
|
|
54
48
|
elif user_input.lower().startswith("/think "):
|
|
55
49
|
try:
|
|
56
50
|
budget = user_input[7:].strip()
|
|
@@ -759,6 +753,37 @@ class CommandProcessor:
|
|
|
759
753
|
self.message_handler._notify("voice_recording_completed", None)
|
|
760
754
|
return CommandResult(handled=True, clear_flag=True)
|
|
761
755
|
|
|
756
|
+
def _handle_debug_command(self, user_input: str) -> CommandResult:
|
|
757
|
+
"""Handle /debug command with optional filtering.
|
|
758
|
+
|
|
759
|
+
Usage:
|
|
760
|
+
/debug - Show both agent and chat messages
|
|
761
|
+
/debug agent - Show only agent messages
|
|
762
|
+
/debug chat - Show only chat/streamline messages
|
|
763
|
+
"""
|
|
764
|
+
parts = user_input.lower().split()
|
|
765
|
+
filter_type = parts[1] if len(parts) > 1 else None
|
|
766
|
+
|
|
767
|
+
if filter_type and filter_type not in ("agent", "chat"):
|
|
768
|
+
self.message_handler._notify(
|
|
769
|
+
"error", f"Invalid filter '{filter_type}'. Use 'agent' or 'chat'."
|
|
770
|
+
)
|
|
771
|
+
return CommandResult(handled=True, clear_flag=True)
|
|
772
|
+
|
|
773
|
+
if filter_type is None or filter_type == "agent":
|
|
774
|
+
self.message_handler._notify(
|
|
775
|
+
"debug_requested",
|
|
776
|
+
{"type": "agent", "messages": self.message_handler.agent.clean_history},
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
if filter_type is None or filter_type == "chat":
|
|
780
|
+
self.message_handler._notify(
|
|
781
|
+
"debug_requested",
|
|
782
|
+
{"type": "chat", "messages": self.message_handler.streamline_messages},
|
|
783
|
+
)
|
|
784
|
+
|
|
785
|
+
return CommandResult(handled=True, clear_flag=True)
|
|
786
|
+
|
|
762
787
|
def _handle_toggle_transfer_command(self, user_input: str) -> CommandResult:
|
|
763
788
|
"""Handle /toggle_transfer command to toggle the enforce_transfer property of agent_manager."""
|
|
764
789
|
try:
|
|
@@ -470,9 +470,13 @@ class MessageHandler(Observable):
|
|
|
470
470
|
if isinstance(e, BadRequestError):
|
|
471
471
|
if e.code == "model_max_prompt_tokens_exceeded":
|
|
472
472
|
from AgentCrew.modules.agents import LocalAgent
|
|
473
|
+
from AgentCrew.modules.llm.model_registry import ModelRegistry
|
|
473
474
|
|
|
474
475
|
if isinstance(self.agent, LocalAgent):
|
|
475
|
-
|
|
476
|
+
max_token = ModelRegistry.get_model_limit(
|
|
477
|
+
self.agent.get_model()
|
|
478
|
+
)
|
|
479
|
+
self.agent.input_tokens_usage = max_token
|
|
476
480
|
return await self.get_assistant_response()
|
|
477
481
|
if self.current_user_input:
|
|
478
482
|
self.conversation_manager.store_conversation_turn(
|
|
@@ -6,7 +6,15 @@ capabilities with platform-specific optimizations.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from .service import CodeAnalysisService
|
|
9
|
+
from .parsers import (
|
|
10
|
+
BaseLanguageParser,
|
|
11
|
+
get_parser_for_language,
|
|
12
|
+
LANGUAGE_PARSER_MAP,
|
|
13
|
+
)
|
|
9
14
|
|
|
10
15
|
__all__ = [
|
|
11
16
|
"CodeAnalysisService",
|
|
17
|
+
"BaseLanguageParser",
|
|
18
|
+
"get_parser_for_language",
|
|
19
|
+
"LANGUAGE_PARSER_MAP",
|
|
12
20
|
]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Language-specific parsers for code analysis.
|
|
3
|
+
|
|
4
|
+
This module provides a unified interface for parsing different programming languages
|
|
5
|
+
using tree-sitter.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .base import BaseLanguageParser
|
|
9
|
+
from .python_parser import PythonParser
|
|
10
|
+
from .javascript_parser import JavaScriptParser
|
|
11
|
+
from .java_parser import JavaParser
|
|
12
|
+
from .cpp_parser import CppParser
|
|
13
|
+
from .ruby_parser import RubyParser
|
|
14
|
+
from .go_parser import GoParser
|
|
15
|
+
from .rust_parser import RustParser
|
|
16
|
+
from .php_parser import PhpParser
|
|
17
|
+
from .csharp_parser import CSharpParser
|
|
18
|
+
from .kotlin_parser import KotlinParser
|
|
19
|
+
from .generic_parser import GenericParser
|
|
20
|
+
|
|
21
|
+
LANGUAGE_PARSER_MAP = {
|
|
22
|
+
"python": PythonParser,
|
|
23
|
+
"javascript": JavaScriptParser,
|
|
24
|
+
"typescript": JavaScriptParser,
|
|
25
|
+
"java": JavaParser,
|
|
26
|
+
"cpp": CppParser,
|
|
27
|
+
"ruby": RubyParser,
|
|
28
|
+
"go": GoParser,
|
|
29
|
+
"rust": RustParser,
|
|
30
|
+
"php": PhpParser,
|
|
31
|
+
"c-sharp": CSharpParser,
|
|
32
|
+
"kotlin": KotlinParser,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_parser_for_language(language: str) -> BaseLanguageParser:
|
|
37
|
+
"""
|
|
38
|
+
Get the appropriate parser for a given language.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
language: The programming language name
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
A parser instance for the language
|
|
45
|
+
"""
|
|
46
|
+
parser_class = LANGUAGE_PARSER_MAP.get(language)
|
|
47
|
+
if parser_class:
|
|
48
|
+
return parser_class()
|
|
49
|
+
return GenericParser(language)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
__all__ = [
|
|
53
|
+
"BaseLanguageParser",
|
|
54
|
+
"PythonParser",
|
|
55
|
+
"JavaScriptParser",
|
|
56
|
+
"JavaParser",
|
|
57
|
+
"CppParser",
|
|
58
|
+
"RubyParser",
|
|
59
|
+
"GoParser",
|
|
60
|
+
"RustParser",
|
|
61
|
+
"PhpParser",
|
|
62
|
+
"CSharpParser",
|
|
63
|
+
"KotlinParser",
|
|
64
|
+
"GenericParser",
|
|
65
|
+
"LANGUAGE_PARSER_MAP",
|
|
66
|
+
"get_parser_for_language",
|
|
67
|
+
]
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base class for language-specific parsers.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BaseLanguageParser(ABC):
|
|
10
|
+
"""Abstract base class for language-specific code parsers."""
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
@abstractmethod
|
|
14
|
+
def language_name(self) -> str:
|
|
15
|
+
"""Return the language name for this parser."""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def extract_node_text(node, source_code: bytes) -> str:
|
|
20
|
+
"""Extract text from a tree-sitter node."""
|
|
21
|
+
return source_code[node.start_byte : node.end_byte].decode("utf-8")
|
|
22
|
+
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def process_node(
|
|
25
|
+
self, node, source_code: bytes, process_children_callback
|
|
26
|
+
) -> Optional[Dict[str, Any]]:
|
|
27
|
+
"""
|
|
28
|
+
Process a tree-sitter node and extract relevant information.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
node: The tree-sitter node to process
|
|
32
|
+
source_code: The source code as bytes
|
|
33
|
+
process_children_callback: Callback to process children nodes recursively
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Dictionary with node information or None if not relevant
|
|
37
|
+
"""
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
def _create_base_result(self, node) -> Dict[str, Any]:
|
|
41
|
+
"""Create the base result dictionary with common fields."""
|
|
42
|
+
return {
|
|
43
|
+
"type": node.type,
|
|
44
|
+
"start_line": node.start_point[0] + 1,
|
|
45
|
+
"end_line": node.end_point[0] + 1,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
def _process_children_default(
|
|
49
|
+
self, node, source_code: bytes, process_children_callback
|
|
50
|
+
) -> List[Dict[str, Any]]:
|
|
51
|
+
"""Default implementation for processing children nodes."""
|
|
52
|
+
children = []
|
|
53
|
+
for child in node.children:
|
|
54
|
+
child_result = process_children_callback(child)
|
|
55
|
+
if child_result and self._is_significant_node(child_result):
|
|
56
|
+
children.append(child_result)
|
|
57
|
+
return children
|
|
58
|
+
|
|
59
|
+
def _is_significant_node(self, result: Dict[str, Any]) -> bool:
|
|
60
|
+
"""Check if a node result is significant and should be included."""
|
|
61
|
+
significant_types = {
|
|
62
|
+
"class_definition",
|
|
63
|
+
"function_definition",
|
|
64
|
+
"class_declaration",
|
|
65
|
+
"method_definition",
|
|
66
|
+
"function_declaration",
|
|
67
|
+
"interface_declaration",
|
|
68
|
+
"method_declaration",
|
|
69
|
+
"constructor_declaration",
|
|
70
|
+
"class_specifier",
|
|
71
|
+
"struct_specifier",
|
|
72
|
+
"struct_declaration",
|
|
73
|
+
"class",
|
|
74
|
+
"method",
|
|
75
|
+
"singleton_method",
|
|
76
|
+
"module",
|
|
77
|
+
"type_declaration",
|
|
78
|
+
"struct_item",
|
|
79
|
+
"impl_item",
|
|
80
|
+
"fn_item",
|
|
81
|
+
"trait_item",
|
|
82
|
+
"trait_declaration",
|
|
83
|
+
"property_declaration",
|
|
84
|
+
"field_declaration",
|
|
85
|
+
"public_field_definition",
|
|
86
|
+
"const_declaration",
|
|
87
|
+
"object_definition",
|
|
88
|
+
"trait_definition",
|
|
89
|
+
"def_definition",
|
|
90
|
+
"variable_declaration",
|
|
91
|
+
"arrow_function",
|
|
92
|
+
}
|
|
93
|
+
return result.get("type") in significant_types or "children" in result
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""
|
|
2
|
+
C++ language parser for code analysis.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
|
|
7
|
+
from .base import BaseLanguageParser
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CppParser(BaseLanguageParser):
|
|
11
|
+
"""Parser for C++ source code."""
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def language_name(self) -> str:
|
|
15
|
+
return "cpp"
|
|
16
|
+
|
|
17
|
+
def process_node(
|
|
18
|
+
self, node, source_code: bytes, process_children_callback
|
|
19
|
+
) -> Optional[Dict[str, Any]]:
|
|
20
|
+
result = self._create_base_result(node)
|
|
21
|
+
|
|
22
|
+
if node.type in ["class_specifier", "struct_specifier"]:
|
|
23
|
+
for child in node.children:
|
|
24
|
+
if child.type in ["identifier", "type_identifier"]:
|
|
25
|
+
result["name"] = self.extract_node_text(child, source_code)
|
|
26
|
+
elif child.type == "field_declaration_list":
|
|
27
|
+
children = []
|
|
28
|
+
for body_child in child.children:
|
|
29
|
+
child_result = process_children_callback(body_child)
|
|
30
|
+
if child_result and self._is_significant_node(child_result):
|
|
31
|
+
children.append(child_result)
|
|
32
|
+
if children:
|
|
33
|
+
result["children"] = children
|
|
34
|
+
return result
|
|
35
|
+
|
|
36
|
+
elif node.type == "function_definition":
|
|
37
|
+
for child in node.children:
|
|
38
|
+
if child.type in ["identifier", "field_identifier"]:
|
|
39
|
+
result["name"] = self.extract_node_text(child, source_code)
|
|
40
|
+
return result
|
|
41
|
+
elif child.type == "function_declarator":
|
|
42
|
+
for subchild in child.children:
|
|
43
|
+
if subchild.type in ["identifier", "field_identifier"]:
|
|
44
|
+
result["name"] = self.extract_node_text(
|
|
45
|
+
subchild, source_code
|
|
46
|
+
)
|
|
47
|
+
return result
|
|
48
|
+
return result
|
|
49
|
+
|
|
50
|
+
elif node.type == "field_declaration":
|
|
51
|
+
return self._handle_field_declaration(node, source_code, result)
|
|
52
|
+
|
|
53
|
+
elif node.type in ["declaration", "variable_declaration"]:
|
|
54
|
+
return self._handle_declaration(node, source_code, result)
|
|
55
|
+
|
|
56
|
+
children = []
|
|
57
|
+
for child in node.children:
|
|
58
|
+
child_result = process_children_callback(child)
|
|
59
|
+
if child_result and self._is_significant_node(child_result):
|
|
60
|
+
children.append(child_result)
|
|
61
|
+
|
|
62
|
+
if children:
|
|
63
|
+
result["children"] = children
|
|
64
|
+
|
|
65
|
+
return result
|
|
66
|
+
|
|
67
|
+
def _handle_field_declaration(
|
|
68
|
+
self, node, source_code: bytes, result: Dict[str, Any]
|
|
69
|
+
) -> Dict[str, Any]:
|
|
70
|
+
field_name = None
|
|
71
|
+
field_type = None
|
|
72
|
+
|
|
73
|
+
for child in node.children:
|
|
74
|
+
if child.type in [
|
|
75
|
+
"primitive_type",
|
|
76
|
+
"type_identifier",
|
|
77
|
+
"qualified_identifier",
|
|
78
|
+
"template_type",
|
|
79
|
+
]:
|
|
80
|
+
field_type = self.extract_node_text(child, source_code)
|
|
81
|
+
elif child.type == "field_identifier":
|
|
82
|
+
field_name = self.extract_node_text(child, source_code)
|
|
83
|
+
|
|
84
|
+
if field_name:
|
|
85
|
+
result["type"] = "field_declaration"
|
|
86
|
+
if field_type:
|
|
87
|
+
result["name"] = f"{field_type} {field_name}"
|
|
88
|
+
else:
|
|
89
|
+
result["name"] = field_name
|
|
90
|
+
|
|
91
|
+
return result
|
|
92
|
+
|
|
93
|
+
def _handle_declaration(
|
|
94
|
+
self, node, source_code: bytes, result: Dict[str, Any]
|
|
95
|
+
) -> Dict[str, Any]:
|
|
96
|
+
var_name = None
|
|
97
|
+
var_type = None
|
|
98
|
+
|
|
99
|
+
for child in node.children:
|
|
100
|
+
if child.type in [
|
|
101
|
+
"primitive_type",
|
|
102
|
+
"type_identifier",
|
|
103
|
+
"qualified_identifier",
|
|
104
|
+
"template_type",
|
|
105
|
+
]:
|
|
106
|
+
var_type = self.extract_node_text(child, source_code)
|
|
107
|
+
elif child.type in ["init_declarator", "declarator"]:
|
|
108
|
+
for subchild in child.children:
|
|
109
|
+
if subchild.type == "identifier":
|
|
110
|
+
var_name = self.extract_node_text(subchild, source_code)
|
|
111
|
+
break
|
|
112
|
+
elif subchild.type == "pointer_declarator":
|
|
113
|
+
for ptr_child in subchild.children:
|
|
114
|
+
if ptr_child.type == "identifier":
|
|
115
|
+
var_name = self.extract_node_text(
|
|
116
|
+
ptr_child, source_code
|
|
117
|
+
)
|
|
118
|
+
break
|
|
119
|
+
|
|
120
|
+
if var_name:
|
|
121
|
+
result["type"] = "variable_declaration"
|
|
122
|
+
if var_type:
|
|
123
|
+
result["name"] = f"{var_type} {var_name}"
|
|
124
|
+
else:
|
|
125
|
+
result["name"] = var_name
|
|
126
|
+
|
|
127
|
+
return result
|