janito 1.9.0__py3-none-any.whl → 1.11.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/__init__.py +1 -1
- janito/agent/api_exceptions.py +4 -0
- janito/agent/config.py +1 -1
- janito/agent/config_defaults.py +2 -26
- janito/agent/conversation.py +163 -122
- janito/agent/conversation_api.py +246 -168
- janito/agent/conversation_ui.py +1 -1
- janito/agent/{conversation_history.py → llm_conversation_history.py} +30 -1
- janito/agent/openai_client.py +38 -23
- janito/agent/openai_schema_generator.py +162 -129
- janito/agent/platform_discovery.py +134 -77
- janito/agent/profile_manager.py +5 -5
- janito/agent/rich_message_handler.py +80 -31
- janito/agent/templates/profiles/system_prompt_template_base.txt.j2 +20 -4
- janito/agent/test_openai_schema_generator.py +93 -0
- janito/agent/tool_base.py +7 -2
- janito/agent/tool_executor.py +54 -49
- janito/agent/tool_registry.py +5 -2
- janito/agent/tool_use_tracker.py +26 -5
- janito/agent/tools/__init__.py +8 -3
- janito/agent/tools/create_directory.py +3 -1
- janito/agent/tools/create_file.py +7 -1
- janito/agent/tools/fetch_url.py +40 -3
- janito/agent/tools/find_files.py +29 -14
- janito/agent/tools/get_file_outline/core.py +7 -8
- janito/agent/tools/get_file_outline/python_outline.py +139 -95
- janito/agent/tools/get_file_outline/search_outline.py +3 -1
- janito/agent/tools/get_lines.py +98 -64
- janito/agent/tools/move_file.py +59 -31
- janito/agent/tools/open_url.py +31 -0
- janito/agent/tools/present_choices.py +3 -1
- janito/agent/tools/python_command_runner.py +149 -0
- janito/agent/tools/python_file_runner.py +147 -0
- janito/agent/tools/python_stdin_runner.py +153 -0
- janito/agent/tools/remove_directory.py +3 -1
- janito/agent/tools/remove_file.py +5 -1
- janito/agent/tools/replace_file.py +12 -2
- janito/agent/tools/replace_text_in_file.py +195 -149
- janito/agent/tools/run_bash_command.py +30 -69
- janito/agent/tools/run_powershell_command.py +138 -105
- janito/agent/tools/search_text/__init__.py +1 -0
- janito/agent/tools/search_text/core.py +176 -0
- janito/agent/tools/search_text/match_lines.py +58 -0
- janito/agent/tools/search_text/pattern_utils.py +65 -0
- janito/agent/tools/search_text/traverse_directory.py +127 -0
- janito/agent/tools/validate_file_syntax/core.py +43 -30
- janito/agent/tools/validate_file_syntax/html_validator.py +21 -5
- janito/agent/tools/validate_file_syntax/markdown_validator.py +77 -34
- janito/agent/tools_utils/action_type.py +7 -0
- janito/agent/tools_utils/dir_walk_utils.py +3 -2
- janito/agent/tools_utils/formatting.py +47 -21
- janito/agent/tools_utils/gitignore_utils.py +89 -40
- janito/agent/tools_utils/test_gitignore_utils.py +46 -0
- janito/agent/tools_utils/utils.py +7 -1
- janito/cli/_print_config.py +63 -61
- janito/cli/arg_parser.py +13 -12
- janito/cli/cli_main.py +137 -147
- janito/cli/config_commands.py +112 -109
- janito/cli/main.py +152 -174
- janito/cli/one_shot.py +40 -26
- janito/i18n/__init__.py +1 -1
- janito/rich_utils.py +46 -8
- janito/shell/commands/__init__.py +2 -4
- janito/shell/commands/conversation_restart.py +3 -1
- janito/shell/commands/edit.py +3 -0
- janito/shell/commands/history_view.py +3 -3
- janito/shell/commands/lang.py +3 -0
- janito/shell/commands/livelogs.py +5 -3
- janito/shell/commands/prompt.py +6 -0
- janito/shell/commands/session.py +3 -0
- janito/shell/commands/session_control.py +3 -0
- janito/shell/commands/termweb_log.py +8 -0
- janito/shell/commands/tools.py +3 -0
- janito/shell/commands/track.py +36 -0
- janito/shell/commands/utility.py +13 -18
- janito/shell/commands/verbose.py +3 -4
- janito/shell/input_history.py +62 -0
- janito/shell/main.py +160 -181
- janito/shell/session/config.py +83 -75
- janito/shell/session/manager.py +0 -21
- janito/shell/ui/interactive.py +97 -75
- janito/termweb/static/editor.css +32 -33
- janito/termweb/static/editor.css.bak +140 -22
- janito/termweb/static/editor.html +12 -7
- janito/termweb/static/editor.html.bak +16 -11
- janito/termweb/static/editor.js +94 -40
- janito/termweb/static/editor.js.bak +97 -65
- janito/termweb/static/index.html +1 -2
- janito/termweb/static/index.html.bak +1 -1
- janito/termweb/static/termweb.css +1 -22
- janito/termweb/static/termweb.css.bak +6 -4
- janito/termweb/static/termweb.js +0 -6
- janito/termweb/static/termweb.js.bak +1 -2
- janito/tests/test_rich_utils.py +44 -0
- janito/web/app.py +0 -75
- {janito-1.9.0.dist-info → janito-1.11.0.dist-info}/METADATA +61 -42
- janito-1.11.0.dist-info/RECORD +163 -0
- {janito-1.9.0.dist-info → janito-1.11.0.dist-info}/WHEEL +1 -1
- janito/agent/providers.py +0 -77
- janito/agent/tools/run_python_command.py +0 -161
- janito/agent/tools/search_text.py +0 -204
- janito/shell/commands/sum.py +0 -49
- janito-1.9.0.dist-info/RECORD +0 -151
- {janito-1.9.0.dist-info → janito-1.11.0.dist-info}/entry_points.txt +0 -0
- {janito-1.9.0.dist-info → janito-1.11.0.dist-info}/licenses/LICENSE +0 -0
- {janito-1.9.0.dist-info → janito-1.11.0.dist-info}/top_level.txt +0 -0
janito/agent/tools/find_files.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
from janito.agent.tool_base import ToolBase
|
2
|
+
from janito.agent.tools_utils.action_type import ActionType
|
2
3
|
from janito.agent.tool_registry import register_tool
|
3
4
|
from janito.agent.tools_utils.utils import pluralize, display_path
|
4
5
|
from janito.agent.tools_utils.dir_walk_utils import walk_dir_with_gitignore
|
@@ -24,6 +25,26 @@ class FindFilesTool(ToolBase):
|
|
24
25
|
If max_results is reached, appends a note to the output.
|
25
26
|
"""
|
26
27
|
|
28
|
+
def _match_directories(self, root, dirs, pat):
|
29
|
+
dir_output = set()
|
30
|
+
dir_pat = pat.rstrip("/\\")
|
31
|
+
for d in dirs:
|
32
|
+
if fnmatch.fnmatch(d, dir_pat):
|
33
|
+
dir_output.add(os.path.join(root, d) + os.sep)
|
34
|
+
return dir_output
|
35
|
+
|
36
|
+
def _match_files(self, root, files, pat):
|
37
|
+
file_output = set()
|
38
|
+
for filename in fnmatch.filter(files, pat):
|
39
|
+
file_output.add(os.path.join(root, filename))
|
40
|
+
return file_output
|
41
|
+
|
42
|
+
def _match_dirs_without_slash(self, root, dirs, pat):
|
43
|
+
dir_output = set()
|
44
|
+
for d in fnmatch.filter(dirs, pat):
|
45
|
+
dir_output.add(os.path.join(root, d))
|
46
|
+
return dir_output
|
47
|
+
|
27
48
|
def run(self, paths: str, pattern: str, max_depth: int = None) -> str:
|
28
49
|
if not pattern:
|
29
50
|
self.report_warning(tr("ℹ️ Empty file pattern provided."))
|
@@ -38,31 +59,26 @@ class FindFilesTool(ToolBase):
|
|
38
59
|
else ""
|
39
60
|
)
|
40
61
|
self.report_info(
|
62
|
+
ActionType.READ,
|
41
63
|
tr(
|
42
|
-
"🔍
|
64
|
+
"🔍 Search for files '{pattern}' in '{disp_path}'{depth_msg} ...",
|
43
65
|
pattern=pattern,
|
44
66
|
disp_path=disp_path,
|
45
67
|
depth_msg=depth_msg,
|
46
|
-
)
|
68
|
+
),
|
47
69
|
)
|
48
70
|
dir_output = set()
|
49
71
|
for root, dirs, files in walk_dir_with_gitignore(
|
50
72
|
directory, max_depth=max_depth
|
51
73
|
):
|
52
74
|
for pat in patterns:
|
53
|
-
# Directory matching: pattern ends with '/' or '\'
|
54
75
|
if pat.endswith("/") or pat.endswith("\\"):
|
55
|
-
|
56
|
-
for d in dirs:
|
57
|
-
if fnmatch.fnmatch(d, dir_pat):
|
58
|
-
dir_output.add(os.path.join(root, d) + os.sep)
|
76
|
+
dir_output.update(self._match_directories(root, dirs, pat))
|
59
77
|
else:
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
for d in fnmatch.filter(dirs, pat):
|
65
|
-
dir_output.add(os.path.join(root, d))
|
78
|
+
dir_output.update(self._match_files(root, files, pat))
|
79
|
+
dir_output.update(
|
80
|
+
self._match_dirs_without_slash(root, dirs, pat)
|
81
|
+
)
|
66
82
|
self.report_success(
|
67
83
|
tr(
|
68
84
|
" ✅ {count} {file_word}",
|
@@ -70,7 +86,6 @@ class FindFilesTool(ToolBase):
|
|
70
86
|
file_word=pluralize("file", len(dir_output)),
|
71
87
|
)
|
72
88
|
)
|
73
|
-
# If searching in '.', strip leading './' from results
|
74
89
|
if directory.strip() == ".":
|
75
90
|
dir_output = {
|
76
91
|
p[2:] if (p.startswith("./") or p.startswith(".\\")) else p
|
@@ -1,12 +1,10 @@
|
|
1
1
|
from janito.agent.tool_registry import register_tool
|
2
2
|
from .python_outline import parse_python_outline
|
3
3
|
from .markdown_outline import parse_markdown_outline
|
4
|
-
from janito.agent.tools_utils.formatting import
|
5
|
-
format_outline_table,
|
6
|
-
format_markdown_outline_table,
|
7
|
-
)
|
4
|
+
from janito.agent.tools_utils.formatting import OutlineFormatter
|
8
5
|
import os
|
9
6
|
from janito.agent.tool_base import ToolBase
|
7
|
+
from janito.agent.tools_utils.action_type import ActionType
|
10
8
|
from janito.agent.tools_utils.utils import display_path, pluralize
|
11
9
|
from janito.i18n import tr
|
12
10
|
|
@@ -23,10 +21,11 @@ class GetFileOutlineTool(ToolBase):
|
|
23
21
|
def run(self, file_path: str) -> str:
|
24
22
|
try:
|
25
23
|
self.report_info(
|
24
|
+
ActionType.READ,
|
26
25
|
tr(
|
27
|
-
"📄
|
26
|
+
"📄 Outline file '{disp_path}' ...",
|
28
27
|
disp_path=display_path(file_path),
|
29
|
-
)
|
28
|
+
),
|
30
29
|
)
|
31
30
|
ext = os.path.splitext(file_path)[1].lower()
|
32
31
|
with open(file_path, "r", encoding="utf-8", errors="replace") as f:
|
@@ -34,7 +33,7 @@ class GetFileOutlineTool(ToolBase):
|
|
34
33
|
if ext == ".py":
|
35
34
|
outline_items = parse_python_outline(lines)
|
36
35
|
outline_type = "python"
|
37
|
-
table = format_outline_table(outline_items)
|
36
|
+
table = OutlineFormatter.format_outline_table(outline_items)
|
38
37
|
self.report_success(
|
39
38
|
tr(
|
40
39
|
"✅ Outlined {count} {item_word}",
|
@@ -53,7 +52,7 @@ class GetFileOutlineTool(ToolBase):
|
|
53
52
|
elif ext == ".md":
|
54
53
|
outline_items = parse_markdown_outline(lines)
|
55
54
|
outline_type = "markdown"
|
56
|
-
table = format_markdown_outline_table(outline_items)
|
55
|
+
table = OutlineFormatter.format_markdown_outline_table(outline_items)
|
57
56
|
self.report_success(
|
58
57
|
tr(
|
59
58
|
"✅ Outlined {count} {item_word}",
|
@@ -2,114 +2,158 @@ import re
|
|
2
2
|
from typing import List
|
3
3
|
|
4
4
|
|
5
|
-
def
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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 build_outline_entry(obj, lines, outline):
|
103
|
+
obj_type, name, start, end, parent, indent = obj
|
104
|
+
# Determine if this is a method
|
105
|
+
if obj_type == "function" and parent:
|
106
|
+
outline_type = "method"
|
107
|
+
elif obj_type == "function":
|
108
|
+
outline_type = "function"
|
109
|
+
else:
|
110
|
+
outline_type = obj_type
|
111
|
+
docstring = extract_docstring(lines, start, end)
|
112
|
+
outline.append(
|
113
|
+
{
|
114
|
+
"type": outline_type,
|
115
|
+
"name": name,
|
116
|
+
"start": start,
|
117
|
+
"end": end,
|
118
|
+
"parent": parent,
|
119
|
+
"docstring": docstring,
|
120
|
+
}
|
121
|
+
)
|
122
|
+
|
123
|
+
|
124
|
+
def process_lines(lines, regexes):
|
10
125
|
outline = []
|
11
|
-
stack = []
|
12
|
-
obj_ranges = []
|
126
|
+
stack = []
|
127
|
+
obj_ranges = []
|
13
128
|
last_top_obj = None
|
14
129
|
for idx, line in enumerate(lines):
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
indent = len(line) - len(line.lstrip())
|
19
|
-
# If a new top-level class or function starts, close the previous one
|
20
|
-
if (class_match or func_match) and indent == 0 and last_top_obj:
|
21
|
-
# Only close if still open
|
22
|
-
if last_top_obj in stack:
|
23
|
-
stack.remove(last_top_obj)
|
24
|
-
obj_ranges.append(
|
25
|
-
(
|
26
|
-
last_top_obj[0],
|
27
|
-
last_top_obj[1],
|
28
|
-
last_top_obj[3],
|
29
|
-
idx,
|
30
|
-
last_top_obj[4],
|
31
|
-
last_top_obj[2],
|
32
|
-
)
|
33
|
-
)
|
34
|
-
last_top_obj = None
|
35
|
-
if class_match:
|
36
|
-
name = class_match.group(2)
|
37
|
-
parent = stack[-1][1] if stack and stack[-1][0] == "class" else ""
|
38
|
-
obj = ("class", name, indent, idx + 1, parent)
|
39
|
-
stack.append(obj)
|
40
|
-
if indent == 0:
|
41
|
-
last_top_obj = obj
|
42
|
-
elif func_match:
|
43
|
-
name = func_match.group(2)
|
44
|
-
parent = ""
|
45
|
-
for s in reversed(stack):
|
46
|
-
if s[0] == "class" and indent > s[2]:
|
47
|
-
parent = s[1]
|
48
|
-
break
|
49
|
-
obj = ("function", name, indent, idx + 1, parent)
|
50
|
-
stack.append(obj)
|
51
|
-
if indent == 0:
|
52
|
-
last_top_obj = obj
|
53
|
-
elif assign_match and indent == 0:
|
54
|
-
var_name = assign_match.group(2)
|
55
|
-
var_type = "const" if var_name.isupper() else "var"
|
56
|
-
outline.append(
|
57
|
-
{
|
58
|
-
"type": var_type,
|
59
|
-
"name": var_name,
|
60
|
-
"start": idx + 1,
|
61
|
-
"end": idx + 1,
|
62
|
-
"parent": "",
|
63
|
-
"docstring": "",
|
64
|
-
}
|
65
|
-
)
|
66
|
-
main_match = main_pat.match(line)
|
67
|
-
if main_match:
|
68
|
-
outline.append(
|
69
|
-
{
|
70
|
-
"type": "main",
|
71
|
-
"name": "__main__",
|
72
|
-
"start": idx + 1,
|
73
|
-
"end": idx + 1,
|
74
|
-
"parent": "",
|
75
|
-
"docstring": "",
|
76
|
-
}
|
77
|
-
)
|
78
|
-
while stack and indent < stack[-1][2]:
|
79
|
-
popped = stack.pop()
|
80
|
-
obj_ranges.append(
|
81
|
-
(popped[0], popped[1], popped[3], idx, popped[4], popped[2])
|
82
|
-
)
|
130
|
+
last_top_obj = process_line(
|
131
|
+
idx, line, regexes, stack, obj_ranges, outline, last_top_obj
|
132
|
+
)
|
83
133
|
# Close any remaining open objects
|
84
134
|
for popped in stack:
|
85
135
|
obj_ranges.append(
|
86
136
|
(popped[0], popped[1], popped[3], len(lines), popped[4], popped[2])
|
87
137
|
)
|
138
|
+
return outline, obj_ranges
|
139
|
+
|
88
140
|
|
89
|
-
|
141
|
+
def build_outline(obj_ranges, lines, outline):
|
90
142
|
for obj in obj_ranges:
|
91
|
-
|
92
|
-
# Determine if this is a method
|
93
|
-
if obj_type == "function" and parent:
|
94
|
-
outline_type = "method"
|
95
|
-
elif obj_type == "function":
|
96
|
-
outline_type = "function"
|
97
|
-
else:
|
98
|
-
outline_type = obj_type
|
99
|
-
docstring = extract_docstring(lines, start, end)
|
100
|
-
outline.append(
|
101
|
-
{
|
102
|
-
"type": outline_type,
|
103
|
-
"name": name,
|
104
|
-
"start": start,
|
105
|
-
"end": end,
|
106
|
-
"parent": parent,
|
107
|
-
"docstring": docstring,
|
108
|
-
}
|
109
|
-
)
|
143
|
+
build_outline_entry(obj, lines, outline)
|
110
144
|
return outline
|
111
145
|
|
112
146
|
|
147
|
+
def parse_python_outline(lines: List[str]):
|
148
|
+
class_pat = re.compile(r"^(\s*)class\s+(\w+)")
|
149
|
+
func_pat = re.compile(r"^(\s*)def\s+(\w+)")
|
150
|
+
assign_pat = re.compile(r"^(\s*)([A-Za-z_][A-Za-z0-9_]*)\s*=.*")
|
151
|
+
main_pat = re.compile(r"^\s*if\s+__name__\s*==\s*[\'\"]__main__[\'\"]\s*:")
|
152
|
+
regexes = (class_pat, func_pat, assign_pat, main_pat)
|
153
|
+
outline, obj_ranges = process_lines(lines, regexes)
|
154
|
+
return build_outline(obj_ranges, lines, outline)
|
155
|
+
|
156
|
+
|
113
157
|
def extract_docstring(lines, start_idx, end_idx):
|
114
158
|
"""Extracts a docstring from lines[start_idx:end_idx] if present."""
|
115
159
|
for i in range(start_idx, min(end_idx, len(lines))):
|
@@ -1,4 +1,5 @@
|
|
1
1
|
from janito.agent.tool_base import ToolBase
|
2
|
+
from janito.agent.tools_utils.action_type import ActionType
|
2
3
|
from janito.agent.tool_registry import register_tool
|
3
4
|
|
4
5
|
|
@@ -13,10 +14,11 @@ class SearchOutlineTool(ToolBase):
|
|
13
14
|
from janito.i18n import tr
|
14
15
|
|
15
16
|
self.report_info(
|
17
|
+
ActionType.READ,
|
16
18
|
tr(
|
17
19
|
"🔍 Searching for outline in '{disp_path}'",
|
18
20
|
disp_path=display_path(file_path),
|
19
|
-
)
|
21
|
+
),
|
20
22
|
)
|
21
23
|
# ... rest of implementation ...
|
22
24
|
# Example warnings and successes:
|
janito/agent/tools/get_lines.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
from janito.agent.tool_base import ToolBase
|
2
|
+
from janito.agent.tools_utils.action_type import ActionType
|
2
3
|
from janito.agent.tool_registry import register_tool
|
3
4
|
from janito.agent.tools_utils.utils import pluralize
|
4
5
|
from janito.i18n import tr
|
@@ -21,95 +22,128 @@ class GetLinesTool(ToolBase):
|
|
21
22
|
- "---\nFile: /path/to/file.py | Lines: 1-10 (of 100)\n---\n<lines...>"
|
22
23
|
- "---\nFile: /path/to/file.py | All lines (total: 100 (all))\n---\n<all lines...>"
|
23
24
|
- "Error reading file: <error message>"
|
24
|
-
- "
|
25
|
+
- "\u2757 not found"
|
25
26
|
"""
|
26
27
|
|
27
28
|
def run(self, file_path: str, from_line: int = None, to_line: int = None) -> str:
|
28
29
|
from janito.agent.tools_utils.utils import display_path
|
29
30
|
|
30
31
|
disp_path = display_path(file_path)
|
32
|
+
self._report_read_info(disp_path, from_line, to_line)
|
33
|
+
try:
|
34
|
+
lines = self._read_file_lines(file_path)
|
35
|
+
selected, selected_len, total_lines = self._select_lines(
|
36
|
+
lines, from_line, to_line
|
37
|
+
)
|
38
|
+
self._report_success(selected_len, from_line, to_line, total_lines)
|
39
|
+
header = self._format_header(
|
40
|
+
disp_path, from_line, to_line, selected_len, total_lines
|
41
|
+
)
|
42
|
+
return header + "".join(selected)
|
43
|
+
except Exception as e:
|
44
|
+
return self._handle_read_error(e)
|
45
|
+
|
46
|
+
def _report_read_info(self, disp_path, from_line, to_line):
|
47
|
+
"""Report the info message for reading lines."""
|
31
48
|
if from_line and to_line:
|
32
49
|
self.report_info(
|
50
|
+
ActionType.READ,
|
33
51
|
tr(
|
34
|
-
"📖
|
52
|
+
"📖 Read file '{disp_path}' {from_line}-{to_line}",
|
35
53
|
disp_path=disp_path,
|
36
54
|
from_line=from_line,
|
37
55
|
to_line=to_line,
|
38
|
-
)
|
56
|
+
),
|
39
57
|
)
|
40
58
|
else:
|
41
|
-
self.report_info(
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
" ✅ {selected_len} {line_word} ({remaining} to end)",
|
67
|
-
selected_len=selected_len,
|
68
|
-
line_word=pluralize("line", selected_len),
|
69
|
-
remaining=total_lines - to_line,
|
70
|
-
)
|
71
|
-
)
|
72
|
-
else:
|
59
|
+
self.report_info(
|
60
|
+
ActionType.READ,
|
61
|
+
tr("📖 Read file '{disp_path}'", disp_path=disp_path),
|
62
|
+
)
|
63
|
+
|
64
|
+
def _read_file_lines(self, file_path):
|
65
|
+
"""Read all lines from the file."""
|
66
|
+
with open(file_path, "r", encoding="utf-8", errors="replace") as f:
|
67
|
+
return f.readlines()
|
68
|
+
|
69
|
+
def _select_lines(self, lines, from_line, to_line):
|
70
|
+
"""Select the requested lines and return them with their count and total lines."""
|
71
|
+
selected = lines[
|
72
|
+
(from_line - 1 if from_line else 0) : (to_line if to_line else None)
|
73
|
+
]
|
74
|
+
selected_len = len(selected)
|
75
|
+
total_lines = len(lines)
|
76
|
+
return selected, selected_len, total_lines
|
77
|
+
|
78
|
+
def _report_success(self, selected_len, from_line, to_line, total_lines):
|
79
|
+
"""Report the success message after reading lines."""
|
80
|
+
if from_line and to_line:
|
81
|
+
requested = to_line - from_line + 1
|
82
|
+
at_end = to_line >= total_lines or selected_len < requested
|
83
|
+
if at_end:
|
73
84
|
self.report_success(
|
74
85
|
tr(
|
75
|
-
"
|
86
|
+
" \u2705 {selected_len} {line_word} (end)",
|
76
87
|
selected_len=selected_len,
|
77
88
|
line_word=pluralize("line", selected_len),
|
78
89
|
)
|
79
90
|
)
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
"
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
)
|
88
|
-
else:
|
89
|
-
header = tr(
|
90
|
-
"---\n{disp_path} {from_line}-{to_line} (of {total_lines})\n---\n",
|
91
|
-
disp_path=disp_path,
|
92
|
-
from_line=from_line,
|
93
|
-
to_line=to_line,
|
94
|
-
total_lines=total_lines,
|
91
|
+
elif to_line < total_lines:
|
92
|
+
self.report_success(
|
93
|
+
tr(
|
94
|
+
" \u2705 {selected_len} {line_word} ({remaining} to end)",
|
95
|
+
selected_len=selected_len,
|
96
|
+
line_word=pluralize("line", selected_len),
|
97
|
+
remaining=total_lines - to_line,
|
95
98
|
)
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
+
)
|
100
|
+
else:
|
101
|
+
self.report_success(
|
102
|
+
tr(
|
103
|
+
" \u2705 {selected_len} {line_word} (all)",
|
104
|
+
selected_len=selected_len,
|
105
|
+
line_word=pluralize("line", selected_len),
|
106
|
+
)
|
107
|
+
)
|
108
|
+
|
109
|
+
def _format_header(self, disp_path, from_line, to_line, selected_len, total_lines):
|
110
|
+
"""Format the header for the output."""
|
111
|
+
if from_line and to_line:
|
112
|
+
requested = to_line - from_line + 1
|
113
|
+
at_end = selected_len < requested or to_line >= total_lines
|
114
|
+
if at_end:
|
115
|
+
return tr(
|
116
|
+
"---\n{disp_path} {from_line}-{to_line} (end)\n---\n",
|
99
117
|
disp_path=disp_path,
|
100
118
|
from_line=from_line,
|
101
|
-
|
119
|
+
to_line=to_line,
|
102
120
|
)
|
103
121
|
else:
|
104
|
-
|
105
|
-
"---\n{disp_path}
|
122
|
+
return tr(
|
123
|
+
"---\n{disp_path} {from_line}-{to_line} (of {total_lines})\n---\n",
|
106
124
|
disp_path=disp_path,
|
125
|
+
from_line=from_line,
|
126
|
+
to_line=to_line,
|
107
127
|
total_lines=total_lines,
|
108
128
|
)
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
129
|
+
elif from_line:
|
130
|
+
return tr(
|
131
|
+
"---\n{disp_path} {from_line}-END (of {total_lines})\n---\n",
|
132
|
+
disp_path=disp_path,
|
133
|
+
from_line=from_line,
|
134
|
+
total_lines=total_lines,
|
135
|
+
)
|
136
|
+
else:
|
137
|
+
return tr(
|
138
|
+
"---\n{disp_path} All lines (total: {total_lines} (all))\n---\n",
|
139
|
+
disp_path=disp_path,
|
140
|
+
total_lines=total_lines,
|
141
|
+
)
|
142
|
+
|
143
|
+
def _handle_read_error(self, e):
|
144
|
+
"""Handle file read errors and report appropriately."""
|
145
|
+
if isinstance(e, FileNotFoundError):
|
146
|
+
self.report_error(tr("\u2757 not found"))
|
147
|
+
return tr("\u2757 not found")
|
148
|
+
self.report_error(tr(" \u274c Error: {error}", error=e))
|
149
|
+
return tr("Error reading file: {error}", error=e)
|