janito 3.2.0__py3-none-any.whl → 3.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.
- janito/README.md +0 -3
- janito/cli/chat_mode/bindings.py +0 -26
- janito/cli/chat_mode/session.py +1 -12
- janito/cli/chat_mode/shell/commands/security/allowed_sites.py +33 -47
- janito/cli/cli_commands/list_plugins.py +8 -13
- janito/cli/core/model_guesser.py +24 -40
- janito/cli/prompt_core.py +9 -20
- janito/i18n/it.py +46 -46
- janito/llm/agent.py +16 -32
- janito/llm/driver.py +0 -8
- janito/{plugin_system → plugin_system_backup_20250825_070018}/core_loader.py +3 -76
- janito/{plugin_system → plugin_system_backup_20250825_070018}/core_loader_fixed.py +3 -79
- janito/plugins/__init__.py +21 -29
- janito/plugins/__main__.py +85 -0
- janito/plugins/base.py +57 -0
- janito/plugins/builtin.py +1 -1
- janito/plugins/core/filemanager/tools/copy_file.py +25 -1
- janito/plugins/core/filemanager/tools/create_directory.py +28 -1
- janito/plugins/core/filemanager/tools/create_file.py +27 -3
- janito/plugins/core/filemanager/tools/delete_text_in_file.py +1 -0
- janito/plugins/core/imagedisplay/plugin.py +1 -1
- janito/plugins/core_loader.py +144 -0
- janito/plugins/discovery.py +3 -3
- janito/plugins/example_plugin.py +1 -1
- janito/plugins/manager.py +1 -1
- janito/plugins_backup_20250825_070018/__init__.py +36 -0
- janito/{plugins → plugins_backup_20250825_070018}/auto_loader.py +11 -12
- janito/plugins_backup_20250825_070018/builtin.py +102 -0
- janito/plugins_backup_20250825_070018/config.py +84 -0
- janito/plugins_backup_20250825_070018/core/__init__.py +7 -0
- janito/plugins_backup_20250825_070018/core/codeanalyzer/__init__.py +43 -0
- janito/plugins_backup_20250825_070018/core/codeanalyzer/tools/get_file_outline/__init__.py +1 -0
- janito/plugins_backup_20250825_070018/core/codeanalyzer/tools/get_file_outline/core.py +122 -0
- janito/plugins_backup_20250825_070018/core/codeanalyzer/tools/search_text/__init__.py +1 -0
- janito/plugins_backup_20250825_070018/core/codeanalyzer/tools/search_text/core.py +205 -0
- janito/plugins_backup_20250825_070018/core/filemanager/__init__.py +124 -0
- janito/plugins_backup_20250825_070018/core/filemanager/tools/copy_file.py +87 -0
- janito/plugins_backup_20250825_070018/core/filemanager/tools/create_directory.py +70 -0
- janito/plugins_backup_20250825_070018/core/filemanager/tools/create_file.py +87 -0
- janito/plugins_backup_20250825_070018/core/filemanager/tools/delete_text_in_file.py +135 -0
- janito/plugins_backup_20250825_070018/core/filemanager/tools/find_files.py +143 -0
- janito/plugins_backup_20250825_070018/core/filemanager/tools/move_file.py +131 -0
- janito/plugins_backup_20250825_070018/core/filemanager/tools/read_files.py +58 -0
- janito/plugins_backup_20250825_070018/core/filemanager/tools/remove_directory.py +55 -0
- janito/plugins_backup_20250825_070018/core/filemanager/tools/remove_file.py +58 -0
- janito/plugins_backup_20250825_070018/core/filemanager/tools/replace_text_in_file.py +270 -0
- janito/plugins_backup_20250825_070018/core/filemanager/tools/validate_file_syntax/__init__.py +1 -0
- janito/plugins_backup_20250825_070018/core/filemanager/tools/validate_file_syntax/core.py +114 -0
- janito/plugins_backup_20250825_070018/core/filemanager/tools/view_file.py +172 -0
- janito/plugins_backup_20250825_070018/core/imagedisplay/__init__.py +14 -0
- janito/plugins_backup_20250825_070018/core/imagedisplay/plugin.py +51 -0
- janito/plugins_backup_20250825_070018/core/imagedisplay/tools/__init__.py +1 -0
- janito/plugins_backup_20250825_070018/core/imagedisplay/tools/show_image.py +83 -0
- janito/plugins_backup_20250825_070018/core/imagedisplay/tools/show_image_grid.py +84 -0
- janito/plugins_backup_20250825_070018/core/system/__init__.py +23 -0
- janito/plugins_backup_20250825_070018/core/system/tools/run_bash_command.py +183 -0
- janito/plugins_backup_20250825_070018/core/system/tools/run_powershell_command.py +218 -0
- janito/plugins_backup_20250825_070018/core_adapter.py +55 -0
- janito/plugins_backup_20250825_070018/dev/__init__.py +7 -0
- janito/plugins_backup_20250825_070018/dev/pythondev/__init__.py +37 -0
- janito/plugins_backup_20250825_070018/dev/pythondev/tools/python_code_run.py +172 -0
- janito/plugins_backup_20250825_070018/dev/pythondev/tools/python_command_run.py +171 -0
- janito/plugins_backup_20250825_070018/dev/pythondev/tools/python_file_run.py +172 -0
- janito/plugins_backup_20250825_070018/dev/visualization/__init__.py +23 -0
- janito/plugins_backup_20250825_070018/dev/visualization/tools/read_chart.py +259 -0
- janito/plugins_backup_20250825_070018/discovery.py +289 -0
- janito/{plugins → plugins_backup_20250825_070018}/discovery_core.py +9 -14
- janito/plugins_backup_20250825_070018/example_plugin.py +108 -0
- janito/plugins_backup_20250825_070018/manager.py +243 -0
- janito/{plugins → plugins_backup_20250825_070018}/tools/core_tools_plugin.py +10 -9
- janito/{plugins → plugins_backup_20250825_070018}/tools/create_file.py +2 -2
- janito/{plugins → plugins_backup_20250825_070018}/tools/delete_text_in_file.py +1 -0
- janito/plugins_backup_20250825_070018/tools/get_file_outline/java_outline.py +47 -0
- janito/plugins_backup_20250825_070018/tools/get_file_outline/markdown_outline.py +14 -0
- janito/plugins_backup_20250825_070018/tools/get_file_outline/python_outline.py +303 -0
- janito/plugins_backup_20250825_070018/tools/get_file_outline/search_outline.py +36 -0
- janito/plugins_backup_20250825_070018/tools/search_text/match_lines.py +67 -0
- janito/plugins_backup_20250825_070018/tools/search_text/pattern_utils.py +73 -0
- janito/plugins_backup_20250825_070018/tools/search_text/traverse_directory.py +145 -0
- janito/plugins_backup_20250825_070018/tools/validate_file_syntax/css_validator.py +35 -0
- janito/plugins_backup_20250825_070018/tools/validate_file_syntax/html_validator.py +100 -0
- janito/plugins_backup_20250825_070018/tools/validate_file_syntax/jinja2_validator.py +50 -0
- janito/plugins_backup_20250825_070018/tools/validate_file_syntax/js_validator.py +27 -0
- janito/plugins_backup_20250825_070018/tools/validate_file_syntax/json_validator.py +6 -0
- janito/plugins_backup_20250825_070018/tools/validate_file_syntax/markdown_validator.py +109 -0
- janito/plugins_backup_20250825_070018/tools/validate_file_syntax/ps1_validator.py +32 -0
- janito/plugins_backup_20250825_070018/tools/validate_file_syntax/python_validator.py +5 -0
- janito/plugins_backup_20250825_070018/tools/validate_file_syntax/xml_validator.py +11 -0
- janito/plugins_backup_20250825_070018/tools/validate_file_syntax/yaml_validator.py +6 -0
- janito/plugins_backup_20250825_070018/ui/__init__.py +7 -0
- janito/plugins_backup_20250825_070018/ui/userinterface/__init__.py +16 -0
- janito/plugins_backup_20250825_070018/ui/userinterface/tools/ask_user.py +110 -0
- janito/plugins_backup_20250825_070018/web/__init__.py +7 -0
- janito/plugins_backup_20250825_070018/web/webtools/__init__.py +33 -0
- janito/plugins_backup_20250825_070018/web/webtools/tools/fetch_url.py +458 -0
- janito/plugins_backup_20250825_070018/web/webtools/tools/open_html_in_browser.py +51 -0
- janito/plugins_backup_20250825_070018/web/webtools/tools/open_url.py +37 -0
- janito/providers/__init__.py +0 -1
- janito/tools/base.py +31 -1
- janito/tools/cli_initializer.py +1 -1
- janito/tools/function_adapter.py +176 -0
- janito/tools/initialize.py +1 -1
- janito/tools/loop_protection_decorator.py +117 -114
- janito/tools/tool_base.py +142 -114
- janito/tools/tools_schema.py +12 -6
- {janito-3.2.0.dist-info → janito-3.3.0.dist-info}/METADATA +1 -1
- {janito-3.2.0.dist-info → janito-3.3.0.dist-info}/RECORD +160 -95
- janito/llm/cancellation_manager.py +0 -63
- janito/llm/enter_cancellation.py +0 -107
- janito/plugins/core_adapter.py +0 -131
- janito/providers/together/__init__.py +0 -1
- janito/providers/together/model_info.py +0 -69
- janito/providers/together/provider.py +0 -108
- /janito/{plugin_system → plugin_system_backup_20250825_070018}/__init__.py +0 -0
- /janito/{plugin_system → plugin_system_backup_20250825_070018}/base.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/auto_loader_fixed.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018/core/codeanalyzer}/tools/get_file_outline/java_outline.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018/core/codeanalyzer}/tools/get_file_outline/markdown_outline.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018/core/codeanalyzer}/tools/get_file_outline/python_outline.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018/core/codeanalyzer}/tools/get_file_outline/search_outline.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018/core/codeanalyzer}/tools/search_text/match_lines.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018/core/codeanalyzer}/tools/search_text/pattern_utils.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018/core/codeanalyzer}/tools/search_text/traverse_directory.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018/core/filemanager}/tools/validate_file_syntax/css_validator.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018/core/filemanager}/tools/validate_file_syntax/html_validator.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018/core/filemanager}/tools/validate_file_syntax/jinja2_validator.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018/core/filemanager}/tools/validate_file_syntax/js_validator.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018/core/filemanager}/tools/validate_file_syntax/json_validator.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018/core/filemanager}/tools/validate_file_syntax/markdown_validator.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018/core/filemanager}/tools/validate_file_syntax/ps1_validator.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018/core/filemanager}/tools/validate_file_syntax/python_validator.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018/core/filemanager}/tools/validate_file_syntax/xml_validator.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018/core/filemanager}/tools/validate_file_syntax/yaml_validator.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/__init__.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/ask_user.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/copy_file.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/create_directory.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/decorators.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/fetch_url.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/find_files.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/get_file_outline/__init__.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/get_file_outline/core.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/move_file.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/open_html_in_browser.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/open_url.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/python_code_run.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/python_command_run.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/python_file_run.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/read_chart.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/read_files.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/remove_directory.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/remove_file.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/replace_text_in_file.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/run_bash_command.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/run_powershell_command.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/search_text/__init__.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/search_text/core.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/show_image.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/show_image_grid.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/validate_file_syntax/__init__.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/validate_file_syntax/core.py +0 -0
- /janito/{plugins → plugins_backup_20250825_070018}/tools/view_file.py +0 -0
- {janito-3.2.0.dist-info → janito-3.3.0.dist-info}/WHEEL +0 -0
- {janito-3.2.0.dist-info → janito-3.3.0.dist-info}/entry_points.txt +0 -0
- {janito-3.2.0.dist-info → janito-3.3.0.dist-info}/licenses/LICENSE +0 -0
- {janito-3.2.0.dist-info → janito-3.3.0.dist-info}/top_level.txt +0 -0
@@ -6,6 +6,16 @@ from functools import wraps
|
|
6
6
|
from typing import Type
|
7
7
|
from janito.plugin_system.base import Plugin, PluginMetadata
|
8
8
|
|
9
|
+
# Registry for core tools
|
10
|
+
_core_tools_registry = []
|
11
|
+
|
12
|
+
|
13
|
+
def register_core_tool(cls: Type):
|
14
|
+
"""Decorator to register a core tool."""
|
15
|
+
_core_tools_registry.append(cls)
|
16
|
+
return cls
|
17
|
+
|
18
|
+
|
9
19
|
from .ask_user import AskUser
|
10
20
|
from .copy_file import CopyFile
|
11
21
|
from .create_directory import CreateDirectory
|
@@ -34,15 +44,6 @@ from .get_file_outline.core import GetFileOutline
|
|
34
44
|
from .search_text.core import SearchText
|
35
45
|
from .decorators import get_core_tools
|
36
46
|
|
37
|
-
# Registry for core tools
|
38
|
-
_core_tools_registry = []
|
39
|
-
|
40
|
-
|
41
|
-
def register_core_tool(cls: Type):
|
42
|
-
"""Decorator to register a core tool."""
|
43
|
-
_core_tools_registry.append(cls)
|
44
|
-
return cls
|
45
|
-
|
46
47
|
|
47
48
|
class CoreToolsPlugin(Plugin):
|
48
49
|
"""Core tools plugin providing essential janito functionality."""
|
@@ -55,7 +55,7 @@ class CreateFile(ToolBase):
|
|
55
55
|
or file exists (when overwrite=False).
|
56
56
|
|
57
57
|
Security Features:
|
58
|
-
- Loop protection:
|
58
|
+
- Loop protection: Maximum 5 calls per 10 seconds for the same file path
|
59
59
|
- Path traversal prevention: Validates and sanitizes file paths
|
60
60
|
- Permission checking: Respects file system permissions
|
61
61
|
- Atomic writes: Prevents partial file creation on errors
|
@@ -84,7 +84,7 @@ class CreateFile(ToolBase):
|
|
84
84
|
permissions = ToolPermissions(write=True)
|
85
85
|
tool_name = "create_file"
|
86
86
|
|
87
|
-
@protect_against_loops(max_calls=
|
87
|
+
@protect_against_loops(max_calls=5, time_window=10.0, key_field="path")
|
88
88
|
def run(self, path: str, content: str, overwrite: bool = False) -> str:
|
89
89
|
path = expand_path(path)
|
90
90
|
disp_path = display_path(path)
|
@@ -15,6 +15,7 @@ class DeleteTextInFile(ToolBase):
|
|
15
15
|
path (str): Path to the file to modify.
|
16
16
|
start_marker (str): The starting delimiter string.
|
17
17
|
end_marker (str): The ending delimiter string.
|
18
|
+
backup (bool, optional): Deprecated. No backups are created anymore and this flag is ignored. Defaults to False.
|
18
19
|
|
19
20
|
Returns:
|
20
21
|
str: Status message indicating the result.
|
@@ -0,0 +1,47 @@
|
|
1
|
+
import re
|
2
|
+
from typing import List, Dict
|
3
|
+
|
4
|
+
|
5
|
+
def parse_java_outline(lines: List[str]) -> List[Dict]:
|
6
|
+
"""
|
7
|
+
Parses Java source code lines and extracts classes and methods with their signatures.
|
8
|
+
Returns a list of outline items: {type, name, return_type, parameters, generics, line}
|
9
|
+
"""
|
10
|
+
outline = []
|
11
|
+
class_pattern = re.compile(r"\bclass\s+(\w+)(\s*<[^>]+>)?")
|
12
|
+
# Match methods with or without visibility modifiers (including package-private)
|
13
|
+
method_pattern = re.compile(
|
14
|
+
r"^(?:\s*(public|protected|private)\s+)?(?:static\s+)?([\w<>\[\]]+)\s+(\w+)\s*\(([^)]*)\)"
|
15
|
+
)
|
16
|
+
current_class = None
|
17
|
+
for idx, line in enumerate(lines, 1):
|
18
|
+
class_match = class_pattern.search(line)
|
19
|
+
if class_match:
|
20
|
+
class_name = class_match.group(1)
|
21
|
+
generics = class_match.group(2) or ""
|
22
|
+
outline.append(
|
23
|
+
{
|
24
|
+
"type": "class",
|
25
|
+
"name": class_name,
|
26
|
+
"generics": generics.strip("<>") if generics else None,
|
27
|
+
"line": idx,
|
28
|
+
}
|
29
|
+
)
|
30
|
+
current_class = class_name
|
31
|
+
else:
|
32
|
+
method_match = method_pattern.search(line)
|
33
|
+
if method_match:
|
34
|
+
return_type = method_match.group(2)
|
35
|
+
method_name = method_match.group(3)
|
36
|
+
params = method_match.group(4)
|
37
|
+
outline.append(
|
38
|
+
{
|
39
|
+
"type": "method",
|
40
|
+
"class": current_class,
|
41
|
+
"name": method_name,
|
42
|
+
"return_type": return_type,
|
43
|
+
"parameters": params.strip(),
|
44
|
+
"line": idx,
|
45
|
+
}
|
46
|
+
)
|
47
|
+
return outline
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import re
|
2
|
+
from typing import List
|
3
|
+
|
4
|
+
|
5
|
+
def parse_markdown_outline(lines: List[str]):
|
6
|
+
header_pat = re.compile(r"^(#+)\s+(.*)")
|
7
|
+
outline = []
|
8
|
+
for idx, line in enumerate(lines):
|
9
|
+
match = header_pat.match(line)
|
10
|
+
if match:
|
11
|
+
level = len(match.group(1))
|
12
|
+
title = match.group(2).strip()
|
13
|
+
outline.append({"level": level, "title": title, "line": idx + 1})
|
14
|
+
return outline
|
@@ -0,0 +1,303 @@
|
|
1
|
+
import re
|
2
|
+
from typing import List
|
3
|
+
|
4
|
+
|
5
|
+
def handle_assignment(idx, assign_match, outline):
|
6
|
+
var_name = assign_match.group(2)
|
7
|
+
var_type = "const" if var_name.isupper() else "var"
|
8
|
+
outline.append(
|
9
|
+
{
|
10
|
+
"type": var_type,
|
11
|
+
"name": var_name,
|
12
|
+
"start": idx + 1,
|
13
|
+
"end": idx + 1,
|
14
|
+
"parent": "",
|
15
|
+
"docstring": "",
|
16
|
+
}
|
17
|
+
)
|
18
|
+
|
19
|
+
|
20
|
+
def handle_main(idx, outline):
|
21
|
+
outline.append(
|
22
|
+
{
|
23
|
+
"type": "main",
|
24
|
+
"name": "__main__",
|
25
|
+
"start": idx + 1,
|
26
|
+
"end": idx + 1,
|
27
|
+
"parent": "",
|
28
|
+
"docstring": "",
|
29
|
+
}
|
30
|
+
)
|
31
|
+
|
32
|
+
|
33
|
+
def close_stack_objects(idx, indent, stack, obj_ranges):
|
34
|
+
while stack and indent < stack[-1][2]:
|
35
|
+
popped = stack.pop()
|
36
|
+
obj_ranges.append((popped[0], popped[1], popped[3], idx, popped[4], popped[2]))
|
37
|
+
|
38
|
+
|
39
|
+
def close_last_top_obj(idx, last_top_obj, stack, obj_ranges):
|
40
|
+
if last_top_obj and last_top_obj in stack:
|
41
|
+
stack.remove(last_top_obj)
|
42
|
+
obj_ranges.append(
|
43
|
+
(
|
44
|
+
last_top_obj[0],
|
45
|
+
last_top_obj[1],
|
46
|
+
last_top_obj[3],
|
47
|
+
idx,
|
48
|
+
last_top_obj[4],
|
49
|
+
last_top_obj[2],
|
50
|
+
)
|
51
|
+
)
|
52
|
+
return None
|
53
|
+
return last_top_obj
|
54
|
+
|
55
|
+
|
56
|
+
def handle_class(idx, class_match, indent, stack, last_top_obj):
|
57
|
+
name = class_match.group(2)
|
58
|
+
parent = stack[-1][1] if stack and stack[-1][0] == "class" else ""
|
59
|
+
obj = ("class", name, indent, idx + 1, parent)
|
60
|
+
stack.append(obj)
|
61
|
+
if indent == 0:
|
62
|
+
last_top_obj = obj
|
63
|
+
return last_top_obj
|
64
|
+
|
65
|
+
|
66
|
+
def handle_function(idx, func_match, indent, stack, last_top_obj):
|
67
|
+
name = func_match.group(2)
|
68
|
+
parent = ""
|
69
|
+
for s in reversed(stack):
|
70
|
+
if s[0] == "class" and indent > s[2]:
|
71
|
+
parent = s[1]
|
72
|
+
break
|
73
|
+
obj = ("function", name, indent, idx + 1, parent)
|
74
|
+
stack.append(obj)
|
75
|
+
if indent == 0:
|
76
|
+
last_top_obj = obj
|
77
|
+
return last_top_obj
|
78
|
+
|
79
|
+
|
80
|
+
def process_line(idx, line, regexes, stack, obj_ranges, outline, last_top_obj):
|
81
|
+
class_pat, func_pat, assign_pat, main_pat = regexes
|
82
|
+
class_match = class_pat.match(line)
|
83
|
+
func_match = func_pat.match(line)
|
84
|
+
assign_match = assign_pat.match(line)
|
85
|
+
indent = len(line) - len(line.lstrip())
|
86
|
+
# If a new top-level class or function starts, close the previous one
|
87
|
+
if (class_match or func_match) and indent == 0 and last_top_obj:
|
88
|
+
last_top_obj = close_last_top_obj(idx, last_top_obj, stack, obj_ranges)
|
89
|
+
if class_match:
|
90
|
+
last_top_obj = handle_class(idx, class_match, indent, stack, last_top_obj)
|
91
|
+
elif func_match:
|
92
|
+
last_top_obj = handle_function(idx, func_match, indent, stack, last_top_obj)
|
93
|
+
elif assign_match and indent == 0:
|
94
|
+
handle_assignment(idx, assign_match, outline)
|
95
|
+
main_match = main_pat.match(line)
|
96
|
+
if main_match:
|
97
|
+
handle_main(idx, outline)
|
98
|
+
close_stack_objects(idx, indent, stack, obj_ranges)
|
99
|
+
return last_top_obj
|
100
|
+
|
101
|
+
|
102
|
+
def extract_signature_and_decorators(lines, start_idx):
|
103
|
+
"""
|
104
|
+
Extracts the signature line and leading decorators for a given function/class/method.
|
105
|
+
Returns (signature:str, decorators:List[str], signature_lineno:int)
|
106
|
+
"""
|
107
|
+
decorators = []
|
108
|
+
sig_line = None
|
109
|
+
sig_lineno = start_idx
|
110
|
+
for i in range(start_idx - 1, -1, -1):
|
111
|
+
striped = lines[i].strip()
|
112
|
+
if striped.startswith("@"):
|
113
|
+
decorators.insert(0, striped)
|
114
|
+
sig_lineno = i
|
115
|
+
elif not striped:
|
116
|
+
continue
|
117
|
+
else:
|
118
|
+
break
|
119
|
+
# Find the signature line itself
|
120
|
+
for k in range(start_idx, len(lines)):
|
121
|
+
striped = lines[k].strip()
|
122
|
+
if striped.startswith("def ") or striped.startswith("class "):
|
123
|
+
sig_line = striped
|
124
|
+
sig_lineno = k
|
125
|
+
break
|
126
|
+
return sig_line, decorators, sig_lineno
|
127
|
+
|
128
|
+
|
129
|
+
def extract_docstring(lines, start_idx, end_idx):
|
130
|
+
"""Extracts a docstring from lines[start_idx:end_idx] if present."""
|
131
|
+
for i in range(start_idx, min(end_idx, len(lines))):
|
132
|
+
line = lines[i].lstrip()
|
133
|
+
if not line:
|
134
|
+
continue
|
135
|
+
if line.startswith('"""') or line.startswith("'''"):
|
136
|
+
quote = line[:3]
|
137
|
+
doc = line[3:]
|
138
|
+
if doc.strip().endswith(quote):
|
139
|
+
return doc.strip()[:-3].strip()
|
140
|
+
docstring_lines = [doc]
|
141
|
+
for j in range(i + 1, min(end_idx, len(lines))):
|
142
|
+
line = lines[j]
|
143
|
+
if line.strip().endswith(quote):
|
144
|
+
docstring_lines.append(line.strip()[:-3])
|
145
|
+
return "\n".join([d.strip() for d in docstring_lines]).strip()
|
146
|
+
docstring_lines.append(line)
|
147
|
+
break
|
148
|
+
else:
|
149
|
+
break
|
150
|
+
return ""
|
151
|
+
|
152
|
+
|
153
|
+
def build_outline_entry(obj, lines, outline):
|
154
|
+
obj_type, name, start, end, parent, indent = obj
|
155
|
+
# Determine if this is a method
|
156
|
+
if obj_type == "function" and parent:
|
157
|
+
outline_type = "method"
|
158
|
+
elif obj_type == "function":
|
159
|
+
outline_type = "function"
|
160
|
+
else:
|
161
|
+
outline_type = obj_type
|
162
|
+
docstring = extract_docstring(lines, start, end)
|
163
|
+
outline.append(
|
164
|
+
{
|
165
|
+
"type": outline_type,
|
166
|
+
"name": name,
|
167
|
+
"start": start,
|
168
|
+
"end": end,
|
169
|
+
"parent": parent,
|
170
|
+
"docstring": docstring,
|
171
|
+
}
|
172
|
+
)
|
173
|
+
|
174
|
+
|
175
|
+
def process_lines(lines, regexes):
|
176
|
+
outline = []
|
177
|
+
stack = []
|
178
|
+
obj_ranges = []
|
179
|
+
last_top_obj = None
|
180
|
+
for idx, line in enumerate(lines):
|
181
|
+
last_top_obj = process_line(
|
182
|
+
idx, line, regexes, stack, obj_ranges, outline, last_top_obj
|
183
|
+
)
|
184
|
+
# Close any remaining open objects
|
185
|
+
for popped in stack:
|
186
|
+
obj_ranges.append(
|
187
|
+
(popped[0], popped[1], popped[3], len(lines), popped[4], popped[2])
|
188
|
+
)
|
189
|
+
return outline, obj_ranges
|
190
|
+
|
191
|
+
|
192
|
+
def build_outline(obj_ranges, lines, outline):
|
193
|
+
for obj in obj_ranges:
|
194
|
+
build_outline_entry(obj, lines, outline)
|
195
|
+
return outline
|
196
|
+
|
197
|
+
|
198
|
+
def parse_python_outline(lines: List[str]):
|
199
|
+
class_pat = re.compile(r"^(\s*)class\s+(\w+)")
|
200
|
+
func_pat = re.compile(r"^(\s*)def\s+(\w+)")
|
201
|
+
assign_pat = re.compile(r"^(\s*)([A-Za-z_][A-Za-z0-9_]*)\s*=.*")
|
202
|
+
main_pat = re.compile(r"^\s*if\s+__name__\s*==\s*[\'\"]__main__[\'\"]\s*:")
|
203
|
+
outline = []
|
204
|
+
stack = []
|
205
|
+
obj_ranges = []
|
206
|
+
last_top_obj = None
|
207
|
+
for idx, line in enumerate(lines):
|
208
|
+
class_match = class_pat.match(line)
|
209
|
+
func_match = func_pat.match(line)
|
210
|
+
assign_match = assign_pat.match(line)
|
211
|
+
indent = len(line) - len(line.lstrip())
|
212
|
+
parent = ""
|
213
|
+
for s in reversed(stack):
|
214
|
+
if s[0] == "class" and indent > s[2]:
|
215
|
+
parent = s[1]
|
216
|
+
break
|
217
|
+
if class_match:
|
218
|
+
obj = ("class", class_match.group(2), idx + 1, None, parent, indent)
|
219
|
+
stack.append(obj)
|
220
|
+
last_top_obj = obj
|
221
|
+
elif func_match:
|
222
|
+
obj = ("function", func_match.group(2), idx + 1, None, parent, indent)
|
223
|
+
stack.append(obj)
|
224
|
+
last_top_obj = obj
|
225
|
+
elif assign_match and indent == 0:
|
226
|
+
outline.append(
|
227
|
+
{
|
228
|
+
"type": "const" if assign_match.group(2).isupper() else "var",
|
229
|
+
"name": assign_match.group(2),
|
230
|
+
"start": idx + 1,
|
231
|
+
"end": idx + 1,
|
232
|
+
"parent": "",
|
233
|
+
"signature": line.strip(),
|
234
|
+
"decorators": [],
|
235
|
+
"docstring": "",
|
236
|
+
}
|
237
|
+
)
|
238
|
+
if line.strip().startswith("if __name__ == "):
|
239
|
+
outline.append(
|
240
|
+
{
|
241
|
+
"type": "main",
|
242
|
+
"name": "__main__",
|
243
|
+
"start": idx + 1,
|
244
|
+
"end": idx + 1,
|
245
|
+
"parent": "",
|
246
|
+
"signature": line.strip(),
|
247
|
+
"decorators": [],
|
248
|
+
"docstring": "",
|
249
|
+
}
|
250
|
+
)
|
251
|
+
# Close stack objects if indent falls back
|
252
|
+
while stack and indent <= stack[-1][5] and idx + 1 > stack[-1][2]:
|
253
|
+
finished = stack.pop()
|
254
|
+
outline_entry = finished[:2] + (
|
255
|
+
finished[2],
|
256
|
+
idx + 1,
|
257
|
+
finished[4],
|
258
|
+
finished[5],
|
259
|
+
)
|
260
|
+
build_outline_entry(outline_entry, lines, outline)
|
261
|
+
# Close any remaining objects
|
262
|
+
while stack:
|
263
|
+
finished = stack.pop()
|
264
|
+
outline_entry = finished[:2] + (
|
265
|
+
finished[2],
|
266
|
+
len(lines),
|
267
|
+
finished[4],
|
268
|
+
finished[5],
|
269
|
+
)
|
270
|
+
build_outline_entry(outline_entry, lines, outline)
|
271
|
+
return outline
|
272
|
+
|
273
|
+
class_pat = re.compile(r"^(\s*)class\s+(\w+)")
|
274
|
+
func_pat = re.compile(r"^(\s*)def\s+(\w+)")
|
275
|
+
assign_pat = re.compile(r"^(\s*)([A-Za-z_][A-Za-z0-9_]*)\s*=.*")
|
276
|
+
main_pat = re.compile(r"^\s*if\s+__name__\s*==\s*[\'\"]__main__[\'\"]\s*:")
|
277
|
+
regexes = (class_pat, func_pat, assign_pat, main_pat)
|
278
|
+
outline, obj_ranges = process_lines(lines, regexes)
|
279
|
+
return build_outline(obj_ranges, lines, outline)
|
280
|
+
|
281
|
+
|
282
|
+
def extract_docstring(lines, start_idx, end_idx):
|
283
|
+
"""Extracts a docstring from lines[start_idx:end_idx] if present."""
|
284
|
+
for i in range(start_idx, min(end_idx, len(lines))):
|
285
|
+
line = lines[i].lstrip()
|
286
|
+
if not line:
|
287
|
+
continue
|
288
|
+
if line.startswith('"""') or line.startswith("'''"):
|
289
|
+
quote = line[:3]
|
290
|
+
doc = line[3:]
|
291
|
+
if doc.strip().endswith(quote):
|
292
|
+
return doc.strip()[:-3].strip()
|
293
|
+
docstring_lines = [doc]
|
294
|
+
for j in range(i + 1, min(end_idx, len(lines))):
|
295
|
+
line = lines[j]
|
296
|
+
if line.strip().endswith(quote):
|
297
|
+
docstring_lines.append(line.strip()[:-3])
|
298
|
+
return "\n".join([d.strip() for d in docstring_lines]).strip()
|
299
|
+
docstring_lines.append(line)
|
300
|
+
break
|
301
|
+
else:
|
302
|
+
break
|
303
|
+
return ""
|
@@ -0,0 +1,36 @@
|
|
1
|
+
from janito.tools.tool_base import ToolBase, ToolPermissions
|
2
|
+
from janito.report_events import ReportAction
|
3
|
+
from janito.tools.loop_protection_decorator import protect_against_loops
|
4
|
+
|
5
|
+
|
6
|
+
class SearchOutlineTool(ToolBase):
|
7
|
+
"""
|
8
|
+
Tool for searching outlines in files.
|
9
|
+
|
10
|
+
Args:
|
11
|
+
path (str): Path to the file for which to generate an outline.
|
12
|
+
Returns:
|
13
|
+
str: Outline search result or status message.
|
14
|
+
"""
|
15
|
+
|
16
|
+
permissions = ToolPermissions(read=True)
|
17
|
+
tool_name = "search_outline"
|
18
|
+
|
19
|
+
@protect_against_loops(max_calls=5, time_window=10.0, key_field="path")
|
20
|
+
def run(self, path: str) -> str:
|
21
|
+
from janito.tools.tool_utils import display_path
|
22
|
+
from janito.i18n import tr
|
23
|
+
|
24
|
+
self.report_action(
|
25
|
+
tr(
|
26
|
+
"🔍 Searching for outline in '{disp_path}'",
|
27
|
+
disp_path=display_path(path),
|
28
|
+
),
|
29
|
+
ReportAction.READ,
|
30
|
+
)
|
31
|
+
# ... rest of implementation ...
|
32
|
+
# Example warnings and successes:
|
33
|
+
# self.report_warning(tr("No files found with supported extensions."))
|
34
|
+
# self.report_warning(tr("Error reading {path}: {error}", path=path, error=e))
|
35
|
+
# self.report_success(tr("✅ {count} {match_word} found", count=len(output), match_word=pluralize('match', len(output))))
|
36
|
+
pass
|
@@ -0,0 +1,67 @@
|
|
1
|
+
import re
|
2
|
+
from janito.gitignore_utils import GitignoreFilter
|
3
|
+
import os
|
4
|
+
|
5
|
+
|
6
|
+
def is_binary_file(path, blocksize=1024):
|
7
|
+
try:
|
8
|
+
with open(path, "rb") as f:
|
9
|
+
chunk = f.read(blocksize)
|
10
|
+
if b"\0" in chunk:
|
11
|
+
return True
|
12
|
+
text_characters = bytearray(
|
13
|
+
{7, 8, 9, 10, 12, 13, 27} | set(range(0x20, 0x100))
|
14
|
+
)
|
15
|
+
nontext = chunk.translate(None, text_characters)
|
16
|
+
if len(nontext) / max(1, len(chunk)) > 0.3:
|
17
|
+
return True
|
18
|
+
except Exception:
|
19
|
+
return True
|
20
|
+
return False
|
21
|
+
|
22
|
+
|
23
|
+
def match_line(line, query, regex, use_regex, case_sensitive):
|
24
|
+
if use_regex:
|
25
|
+
return regex and regex.search(line)
|
26
|
+
if not case_sensitive:
|
27
|
+
return query.lower() in line.lower()
|
28
|
+
return query in line
|
29
|
+
|
30
|
+
|
31
|
+
def should_limit(max_results, total_results, match_count, count_only, dir_output):
|
32
|
+
if max_results > 0:
|
33
|
+
current_count = total_results + (match_count if count_only else len(dir_output))
|
34
|
+
return current_count >= max_results
|
35
|
+
return False
|
36
|
+
|
37
|
+
|
38
|
+
def read_file_lines(
|
39
|
+
path,
|
40
|
+
query,
|
41
|
+
regex,
|
42
|
+
use_regex,
|
43
|
+
case_sensitive,
|
44
|
+
count_only,
|
45
|
+
max_results,
|
46
|
+
total_results,
|
47
|
+
):
|
48
|
+
dir_output = []
|
49
|
+
dir_limit_reached = False
|
50
|
+
match_count = 0
|
51
|
+
if not is_binary_file(path):
|
52
|
+
try:
|
53
|
+
open_kwargs = {"mode": "r", "encoding": "utf-8"}
|
54
|
+
with open(path, **open_kwargs) as f:
|
55
|
+
for lineno, line in enumerate(f, 1):
|
56
|
+
if match_line(line, query, regex, use_regex, case_sensitive):
|
57
|
+
match_count += 1
|
58
|
+
if not count_only:
|
59
|
+
dir_output.append(f"{path}:{lineno}: {line.rstrip()}")
|
60
|
+
if should_limit(
|
61
|
+
max_results, total_results, match_count, count_only, dir_output
|
62
|
+
):
|
63
|
+
dir_limit_reached = True
|
64
|
+
break
|
65
|
+
except Exception:
|
66
|
+
pass
|
67
|
+
return match_count, dir_limit_reached, dir_output
|
@@ -0,0 +1,73 @@
|
|
1
|
+
import re
|
2
|
+
from janito.i18n import tr
|
3
|
+
from janito.tools.tool_utils import pluralize
|
4
|
+
|
5
|
+
|
6
|
+
def prepare_pattern(pattern, is_regex, case_sensitive, report_error, report_warning):
|
7
|
+
if not pattern:
|
8
|
+
report_error(
|
9
|
+
tr("Error: Empty search pattern provided. Operation aborted."),
|
10
|
+
ReportAction.SEARCH,
|
11
|
+
)
|
12
|
+
return (
|
13
|
+
None,
|
14
|
+
False,
|
15
|
+
tr("Error: Empty search pattern provided. Operation aborted."),
|
16
|
+
)
|
17
|
+
regex = None
|
18
|
+
use_regex = False
|
19
|
+
if is_regex:
|
20
|
+
try:
|
21
|
+
flags = 0
|
22
|
+
if not case_sensitive:
|
23
|
+
flags |= re.IGNORECASE
|
24
|
+
regex = re.compile(pattern, flags=flags)
|
25
|
+
use_regex = True
|
26
|
+
except re.error as e:
|
27
|
+
report_warning(tr("⚠️ Invalid regex pattern."))
|
28
|
+
return (
|
29
|
+
None,
|
30
|
+
False,
|
31
|
+
tr(
|
32
|
+
"Error: Invalid regex pattern: {error}. Operation aborted.", error=e
|
33
|
+
),
|
34
|
+
)
|
35
|
+
else:
|
36
|
+
# Do not compile as regex if is_regex is False; treat as plain text
|
37
|
+
regex = None
|
38
|
+
use_regex = False
|
39
|
+
if not case_sensitive:
|
40
|
+
pattern = pattern.lower()
|
41
|
+
return regex, use_regex, None
|
42
|
+
|
43
|
+
|
44
|
+
def format_result(
|
45
|
+
query, use_regex, output, limit_reached, count_only=False, per_file_counts=None
|
46
|
+
):
|
47
|
+
# Ensure output is always a list for joining
|
48
|
+
if output is None or not isinstance(output, (list, tuple)):
|
49
|
+
output = []
|
50
|
+
if count_only:
|
51
|
+
lines = []
|
52
|
+
total = 0
|
53
|
+
if per_file_counts:
|
54
|
+
for path, count in per_file_counts:
|
55
|
+
lines.append(f"{path}: {count}")
|
56
|
+
total += count
|
57
|
+
lines.append(f"Total matches: {total}")
|
58
|
+
if limit_reached:
|
59
|
+
lines.append(tr("[Max results reached. Output truncated.]"))
|
60
|
+
return "\n".join(lines)
|
61
|
+
else:
|
62
|
+
if not output:
|
63
|
+
return tr("No matches found.")
|
64
|
+
result = "\n".join(output)
|
65
|
+
if limit_reached:
|
66
|
+
result += tr("\n[Max results reached. Output truncated.]")
|
67
|
+
return result
|
68
|
+
|
69
|
+
|
70
|
+
def summarize_total(all_per_file_counts):
|
71
|
+
total = sum(count for _, count in all_per_file_counts)
|
72
|
+
summary = f"\nGrand total matches: {total}"
|
73
|
+
return summary
|