janito 2.5.1__py3-none-any.whl → 2.6.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/agent/setup_agent.py +231 -223
- janito/agent/templates/profiles/system_prompt_template_software_developer.txt.j2 +39 -0
- janito/cli/chat_mode/bindings.py +1 -26
- janito/cli/chat_mode/session.py +282 -294
- janito/cli/chat_mode/session_profile_select.py +125 -55
- janito/cli/chat_mode/shell/commands/tools.py +51 -48
- janito/cli/chat_mode/toolbar.py +42 -68
- janito/cli/cli_commands/list_tools.py +41 -56
- janito/cli/cli_commands/show_system_prompt.py +70 -49
- janito/cli/core/runner.py +6 -1
- janito/cli/core/setters.py +43 -34
- janito/cli/main_cli.py +25 -1
- janito/cli/prompt_core.py +76 -69
- janito/cli/rich_terminal_reporter.py +22 -1
- janito/cli/single_shot_mode/handler.py +95 -94
- janito/drivers/driver_registry.py +27 -29
- janito/drivers/openai/driver.py +436 -494
- janito/llm/agent.py +54 -68
- janito/provider_registry.py +178 -178
- janito/providers/anthropic/model_info.py +41 -22
- janito/providers/anthropic/provider.py +80 -67
- janito/providers/provider_static_info.py +18 -17
- janito/tools/adapters/local/__init__.py +66 -65
- janito/tools/adapters/local/adapter.py +79 -18
- janito/tools/adapters/local/create_directory.py +9 -9
- janito/tools/adapters/local/create_file.py +12 -12
- janito/tools/adapters/local/delete_text_in_file.py +16 -16
- janito/tools/adapters/local/find_files.py +2 -2
- janito/tools/adapters/local/get_file_outline/core.py +5 -5
- janito/tools/adapters/local/get_file_outline/search_outline.py +4 -4
- janito/tools/adapters/local/open_html_in_browser.py +15 -15
- janito/tools/adapters/local/python_file_run.py +4 -4
- janito/tools/adapters/local/read_files.py +40 -0
- janito/tools/adapters/local/remove_directory.py +5 -5
- janito/tools/adapters/local/remove_file.py +4 -4
- janito/tools/adapters/local/replace_text_in_file.py +21 -21
- janito/tools/adapters/local/run_bash_command.py +1 -1
- janito/tools/adapters/local/search_text/pattern_utils.py +2 -2
- janito/tools/adapters/local/search_text/traverse_directory.py +10 -10
- janito/tools/adapters/local/validate_file_syntax/core.py +7 -7
- janito/tools/adapters/local/validate_file_syntax/css_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/html_validator.py +7 -7
- janito/tools/adapters/local/validate_file_syntax/js_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/json_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/markdown_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/ps1_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/python_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/xml_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/yaml_validator.py +2 -2
- janito/tools/adapters/local/view_file.py +12 -12
- janito/tools/path_security.py +204 -0
- janito/tools/tool_use_tracker.py +12 -12
- janito/tools/tools_adapter.py +66 -34
- {janito-2.5.1.dist-info → janito-2.6.0.dist-info}/METADATA +412 -412
- {janito-2.5.1.dist-info → janito-2.6.0.dist-info}/RECORD +59 -58
- janito/drivers/anthropic/driver.py +0 -113
- janito/tools/adapters/local/get_file_outline/python_outline_v2.py +0 -156
- {janito-2.5.1.dist-info → janito-2.6.0.dist-info}/WHEEL +0 -0
- {janito-2.5.1.dist-info → janito-2.6.0.dist-info}/entry_points.txt +0 -0
- {janito-2.5.1.dist-info → janito-2.6.0.dist-info}/licenses/LICENSE +0 -0
- {janito-2.5.1.dist-info → janito-2.6.0.dist-info}/top_level.txt +0 -0
@@ -50,7 +50,7 @@ class FindFilesTool(ToolBase):
|
|
50
50
|
dir_output.add(os.path.join(root, d))
|
51
51
|
return dir_output
|
52
52
|
|
53
|
-
def
|
53
|
+
def _handle_path(self, directory, patterns):
|
54
54
|
dir_output = set()
|
55
55
|
filename = os.path.basename(directory)
|
56
56
|
for pat in patterns:
|
@@ -129,7 +129,7 @@ class FindFilesTool(ToolBase):
|
|
129
129
|
self._report_search(pattern, disp_path, depth_msg)
|
130
130
|
dir_output = set()
|
131
131
|
if os.path.isfile(directory):
|
132
|
-
dir_output = self.
|
132
|
+
dir_output = self._handle_path(directory, patterns)
|
133
133
|
elif os.path.isdir(directory):
|
134
134
|
dir_output = self._handle_directory_path(directory, patterns, max_depth, include_gitignored)
|
135
135
|
self._report_success(len(dir_output))
|
@@ -18,22 +18,22 @@ class GetFileOutlineTool(ToolBase):
|
|
18
18
|
Get an outline of a file's structure. Supports Python and Markdown files.
|
19
19
|
|
20
20
|
Args:
|
21
|
-
|
21
|
+
path (str): Path to the file to outline.
|
22
22
|
"""
|
23
23
|
permissions = ToolPermissions(read=True)
|
24
24
|
tool_name = "get_file_outline"
|
25
25
|
|
26
|
-
def run(self,
|
26
|
+
def run(self, path: str) -> str:
|
27
27
|
try:
|
28
28
|
self.report_action(
|
29
29
|
tr(
|
30
30
|
"📄 Outline file '{disp_path}' ...",
|
31
|
-
disp_path=display_path(
|
31
|
+
disp_path=display_path(path),
|
32
32
|
),
|
33
33
|
ReportAction.READ,
|
34
34
|
)
|
35
|
-
ext = os.path.splitext(
|
36
|
-
with open(
|
35
|
+
ext = os.path.splitext(path)[1].lower()
|
36
|
+
with open(path, "r", encoding="utf-8", errors="replace") as f:
|
37
37
|
lines = f.readlines()
|
38
38
|
return self._outline_by_extension(ext, lines)
|
39
39
|
except Exception as e:
|
@@ -7,27 +7,27 @@ class SearchOutlineTool(ToolBase):
|
|
7
7
|
Tool for searching outlines in files.
|
8
8
|
|
9
9
|
Args:
|
10
|
-
|
10
|
+
path (str): Path to the file for which to generate an outline.
|
11
11
|
Returns:
|
12
12
|
str: Outline search result or status message.
|
13
13
|
"""
|
14
14
|
permissions = ToolPermissions(read=True)
|
15
15
|
tool_name = "search_outline"
|
16
16
|
|
17
|
-
def run(self,
|
17
|
+
def run(self, path: str) -> str:
|
18
18
|
from janito.tools.tool_utils import display_path
|
19
19
|
from janito.i18n import tr
|
20
20
|
|
21
21
|
self.report_action(
|
22
22
|
tr(
|
23
23
|
"🔍 Searching for outline in '{disp_path}'",
|
24
|
-
disp_path=display_path(
|
24
|
+
disp_path=display_path(path),
|
25
25
|
),
|
26
26
|
ReportAction.READ,
|
27
27
|
)
|
28
28
|
# ... rest of implementation ...
|
29
29
|
# Example warnings and successes:
|
30
30
|
# self.report_warning(tr("No files found with supported extensions."))
|
31
|
-
# self.report_warning(tr("Error reading {
|
31
|
+
# self.report_warning(tr("Error reading {path}: {error}", path=path, error=e))
|
32
32
|
# self.report_success(tr("✅ {count} {match_word} found", count=len(output), match_word=pluralize('match', len(output))))
|
33
33
|
pass
|
@@ -11,29 +11,29 @@ class OpenHtmlInBrowserTool(ToolBase):
|
|
11
11
|
Open the supplied HTML file in the default web browser.
|
12
12
|
|
13
13
|
Args:
|
14
|
-
|
14
|
+
path (str): Path to the HTML file to open.
|
15
15
|
Returns:
|
16
16
|
str: Status message indicating the result.
|
17
17
|
"""
|
18
18
|
permissions = ToolPermissions(read=True)
|
19
19
|
tool_name = "open_html_in_browser"
|
20
20
|
|
21
|
-
def run(self,
|
22
|
-
if not
|
21
|
+
def run(self, path: str) -> str:
|
22
|
+
if not path.strip():
|
23
23
|
self.report_warning(tr("ℹ️ Empty file path provided."))
|
24
24
|
return tr("Warning: Empty file path provided. Operation skipped.")
|
25
|
-
if not os.path.isfile(
|
26
|
-
self.report_error(tr("❗ File does not exist: {
|
27
|
-
return tr("Warning: File does not exist: {
|
28
|
-
if not
|
29
|
-
self.report_warning(tr("⚠️ Not an HTML file: {
|
30
|
-
return tr("Warning: Not an HTML file: {
|
31
|
-
url = 'file://' + os.path.abspath(
|
32
|
-
self.report_action(tr("📖 Opening HTML file in browser: {
|
25
|
+
if not os.path.isfile(path):
|
26
|
+
self.report_error(tr("❗ File does not exist: {path}", path=path))
|
27
|
+
return tr("Warning: File does not exist: {path}", path=path)
|
28
|
+
if not path.lower().endswith(('.html', '.htm')):
|
29
|
+
self.report_warning(tr("⚠️ Not an HTML file: {path}", path=path))
|
30
|
+
return tr("Warning: Not an HTML file: {path}", path=path)
|
31
|
+
url = 'file://' + os.path.abspath(path)
|
32
|
+
self.report_action(tr("📖 Opening HTML file in browser: {path}", path=path), ReportAction.READ)
|
33
33
|
try:
|
34
34
|
webbrowser.open(url)
|
35
35
|
except Exception as err:
|
36
|
-
self.report_error(tr("❗ Error opening HTML file: {
|
37
|
-
return tr("Warning: Error opening HTML file: {
|
38
|
-
self.report_success(tr("✅ HTML file opened in browser: {
|
39
|
-
return tr("HTML file opened in browser: {
|
36
|
+
self.report_error(tr("❗ Error opening HTML file: {path}: {err}", path=path, err=str(err)))
|
37
|
+
return tr("Warning: Error opening HTML file: {path}: {err}", path=path, err=str(err))
|
38
|
+
self.report_success(tr("✅ HTML file opened in browser: {path}", path=path))
|
39
|
+
return tr("HTML file opened in browser: {path}", path=path)
|
@@ -15,7 +15,7 @@ class PythonFileRunTool(ToolBase):
|
|
15
15
|
Tool to execute a specified Python script file.
|
16
16
|
|
17
17
|
Args:
|
18
|
-
|
18
|
+
path (str): Path to the Python script file to execute.
|
19
19
|
timeout (int): Timeout in seconds for the command. Defaults to 60.
|
20
20
|
|
21
21
|
Returns:
|
@@ -24,9 +24,9 @@ class PythonFileRunTool(ToolBase):
|
|
24
24
|
permissions = ToolPermissions(execute=True)
|
25
25
|
tool_name = "python_file_run"
|
26
26
|
|
27
|
-
def run(self,
|
27
|
+
def run(self, path: str, timeout: int = 60) -> str:
|
28
28
|
self.report_action(
|
29
|
-
tr("🚀 Running: python {
|
29
|
+
tr("🚀 Running: python {path}", path=path),
|
30
30
|
ReportAction.EXECUTE,
|
31
31
|
)
|
32
32
|
self.report_stdout("\n")
|
@@ -46,7 +46,7 @@ class PythonFileRunTool(ToolBase):
|
|
46
46
|
) as stderr_file,
|
47
47
|
):
|
48
48
|
process = subprocess.Popen(
|
49
|
-
[sys.executable,
|
49
|
+
[sys.executable, path],
|
50
50
|
stdout=subprocess.PIPE,
|
51
51
|
stderr=subprocess.PIPE,
|
52
52
|
text=True,
|
@@ -0,0 +1,40 @@
|
|
1
|
+
from janito.tools.tool_base import ToolBase, ToolPermissions
|
2
|
+
from janito.report_events import ReportAction
|
3
|
+
from janito.tools.adapters.local.adapter import register_local_tool
|
4
|
+
from janito.tools.tool_utils import pluralize
|
5
|
+
from janito.i18n import tr
|
6
|
+
|
7
|
+
@register_local_tool
|
8
|
+
class ReadFilesTool(ToolBase):
|
9
|
+
"""
|
10
|
+
Read all text content from multiple files.
|
11
|
+
|
12
|
+
Args:
|
13
|
+
paths (list[str]): List of file paths to read.
|
14
|
+
|
15
|
+
Returns:
|
16
|
+
str: Concatenated content of all files, each prefixed by a header with the file name. If a file cannot be read, an error message is included for that file.
|
17
|
+
"""
|
18
|
+
permissions = ToolPermissions(read=True)
|
19
|
+
tool_name = "read_files"
|
20
|
+
|
21
|
+
def run(self, paths: list[str]) -> str:
|
22
|
+
from janito.tools.tool_utils import display_path
|
23
|
+
import os
|
24
|
+
results = []
|
25
|
+
for path in paths:
|
26
|
+
disp_path = display_path(path)
|
27
|
+
self.report_action(tr("📖 Read '{disp_path}'", disp_path=disp_path), ReportAction.READ)
|
28
|
+
if not os.path.isfile(path):
|
29
|
+
self.report_warning(tr("❗ not found: {disp_path}", disp_path=disp_path))
|
30
|
+
results.append(f"--- File: {disp_path} (not found) ---\n")
|
31
|
+
continue
|
32
|
+
try:
|
33
|
+
with open(path, "r", encoding="utf-8", errors="replace") as f:
|
34
|
+
content = f.read()
|
35
|
+
results.append(f"--- File: {disp_path} ---\n{content}\n")
|
36
|
+
self.report_success(tr("✅ Read {disp_path}", disp_path=disp_path))
|
37
|
+
except Exception as e:
|
38
|
+
self.report_error(tr(" ❌ Error reading {disp_path}: {error}", disp_path=disp_path, error=e))
|
39
|
+
results.append(f"--- File: {disp_path} (error) ---\nError reading file: {e}\n")
|
40
|
+
return "\n".join(results)
|
@@ -14,7 +14,7 @@ class RemoveDirectoryTool(ToolBase):
|
|
14
14
|
Remove a directory.
|
15
15
|
|
16
16
|
Args:
|
17
|
-
|
17
|
+
path (str): Path to the directory to remove.
|
18
18
|
recursive (bool, optional): If True, remove non-empty directories recursively (with backup). If False, only remove empty directories. Defaults to False.
|
19
19
|
Returns:
|
20
20
|
str: Status message indicating result. Example:
|
@@ -24,8 +24,8 @@ class RemoveDirectoryTool(ToolBase):
|
|
24
24
|
permissions = ToolPermissions(write=True)
|
25
25
|
tool_name = "remove_directory"
|
26
26
|
|
27
|
-
def run(self,
|
28
|
-
disp_path = display_path(
|
27
|
+
def run(self, path: str, recursive: bool = False) -> str:
|
28
|
+
disp_path = display_path(path)
|
29
29
|
self.report_action(
|
30
30
|
tr("🗃️ Remove directory '{disp_path}' ...", disp_path=disp_path),
|
31
31
|
ReportAction.DELETE,
|
@@ -34,9 +34,9 @@ class RemoveDirectoryTool(ToolBase):
|
|
34
34
|
try:
|
35
35
|
if recursive:
|
36
36
|
|
37
|
-
shutil.rmtree(
|
37
|
+
shutil.rmtree(path)
|
38
38
|
else:
|
39
|
-
os.rmdir(
|
39
|
+
os.rmdir(path)
|
40
40
|
self.report_success(
|
41
41
|
tr("✅ 1 {dir_word}", dir_word=pluralize("directory", 1)),
|
42
42
|
ReportAction.DELETE,
|
@@ -14,7 +14,7 @@ class RemoveFileTool(ToolBase):
|
|
14
14
|
Remove a file at the specified path.
|
15
15
|
|
16
16
|
Args:
|
17
|
-
|
17
|
+
path (str): Path to the file to remove.
|
18
18
|
backup (bool, optional): Deprecated. Backups are no longer created. Flag ignored.
|
19
19
|
Returns:
|
20
20
|
str: Status message indicating the result. Example:
|
@@ -24,9 +24,9 @@ class RemoveFileTool(ToolBase):
|
|
24
24
|
permissions = ToolPermissions(write=True)
|
25
25
|
tool_name = "remove_file"
|
26
26
|
|
27
|
-
def run(self,
|
28
|
-
original_path =
|
29
|
-
path =
|
27
|
+
def run(self, path: str, backup: bool = False) -> str:
|
28
|
+
original_path = path
|
29
|
+
path = path # Using path as is
|
30
30
|
disp_path = display_path(original_path)
|
31
31
|
|
32
32
|
# Report initial info about what is going to be removed
|
@@ -17,7 +17,7 @@ class ReplaceTextInFileTool(ToolBase):
|
|
17
17
|
search text in its original location.
|
18
18
|
|
19
19
|
Args:
|
20
|
-
|
20
|
+
path (str): Path to the file to modify.
|
21
21
|
search_text (str): The exact text to search for (including indentation).
|
22
22
|
replacement_text (str): The text to replace with (including indentation).
|
23
23
|
replace_all (bool): If True, replace all occurrences; otherwise, only the first occurrence.
|
@@ -33,7 +33,7 @@ class ReplaceTextInFileTool(ToolBase):
|
|
33
33
|
|
34
34
|
def run(
|
35
35
|
self,
|
36
|
-
|
36
|
+
path: str,
|
37
37
|
search_text: str,
|
38
38
|
replacement_text: str,
|
39
39
|
replace_all: bool = False,
|
@@ -41,7 +41,7 @@ class ReplaceTextInFileTool(ToolBase):
|
|
41
41
|
) -> str:
|
42
42
|
from janito.tools.tool_utils import display_path
|
43
43
|
|
44
|
-
disp_path = display_path(
|
44
|
+
disp_path = display_path(path)
|
45
45
|
action = "(all)" if replace_all else ""
|
46
46
|
search_lines = len(search_text.splitlines())
|
47
47
|
replace_lines = len(replacement_text.splitlines())
|
@@ -52,11 +52,11 @@ class ReplaceTextInFileTool(ToolBase):
|
|
52
52
|
action,
|
53
53
|
search_text,
|
54
54
|
replacement_text,
|
55
|
-
|
55
|
+
path,
|
56
56
|
)
|
57
57
|
self.report_action(info_msg, ReportAction.CREATE)
|
58
58
|
try:
|
59
|
-
content = self._read_file_content(
|
59
|
+
content = self._read_file_content(path)
|
60
60
|
match_lines = self._find_match_lines(content, search_text)
|
61
61
|
occurrences = content.count(search_text)
|
62
62
|
replaced_count, new_content = self._replace_content(
|
@@ -66,9 +66,9 @@ class ReplaceTextInFileTool(ToolBase):
|
|
66
66
|
backup_path = None
|
67
67
|
validation_result = ""
|
68
68
|
if file_changed:
|
69
|
-
self._write_file_content(
|
69
|
+
self._write_file_content(path, new_content)
|
70
70
|
# Perform syntax validation and append result
|
71
|
-
validation_result = validate_file_syntax(
|
71
|
+
validation_result = validate_file_syntax(path)
|
72
72
|
warning, concise_warning = self._handle_warnings(
|
73
73
|
replaced_count, file_changed, occurrences
|
74
74
|
)
|
@@ -86,15 +86,15 @@ class ReplaceTextInFileTool(ToolBase):
|
|
86
86
|
replace_all,
|
87
87
|
)
|
88
88
|
return self._format_final_msg(
|
89
|
-
|
89
|
+
path, warning, match_info, details
|
90
90
|
) + (f"\n{validation_result}" if validation_result else "")
|
91
91
|
except Exception as e:
|
92
92
|
self.report_error(tr(" ❌ Error"), ReportAction.REPLACE)
|
93
93
|
return tr("Error replacing text: {error}", error=e)
|
94
94
|
|
95
|
-
def _read_file_content(self,
|
95
|
+
def _read_file_content(self, path):
|
96
96
|
"""Read the entire content of the file."""
|
97
|
-
with open(
|
97
|
+
with open(path, "r", encoding="utf-8", errors="replace") as f:
|
98
98
|
return f.read()
|
99
99
|
|
100
100
|
def _find_match_lines(self, content, search_text):
|
@@ -127,13 +127,13 @@ class ReplaceTextInFileTool(ToolBase):
|
|
127
127
|
new_content = content.replace(search_text, replacement_text, 1)
|
128
128
|
return replaced_count, new_content
|
129
129
|
|
130
|
-
def _backup_file(self,
|
130
|
+
def _backup_file(self, path, backup_path):
|
131
131
|
"""Create a backup of the file."""
|
132
|
-
shutil.copy2(
|
132
|
+
shutil.copy2(path, backup_path)
|
133
133
|
|
134
|
-
def _write_file_content(self,
|
134
|
+
def _write_file_content(self, path, content):
|
135
135
|
"""Write content to the file."""
|
136
|
-
with open(
|
136
|
+
with open(path, "w", encoding="utf-8", errors="replace") as f:
|
137
137
|
f.write(content)
|
138
138
|
|
139
139
|
def _handle_warnings(self, replaced_count, file_changed, occurrences):
|
@@ -144,14 +144,14 @@ class ReplaceTextInFileTool(ToolBase):
|
|
144
144
|
warning = tr(" [Warning: Search text not found in file]")
|
145
145
|
if not file_changed:
|
146
146
|
self.report_warning(
|
147
|
-
tr(" ℹ️
|
147
|
+
tr(" ℹ️ No changes made. (not found)"), ReportAction.CREATE
|
148
148
|
)
|
149
149
|
concise_warning = tr(
|
150
150
|
"No changes made. The search text was not found. Expand your search context with surrounding lines if needed."
|
151
151
|
)
|
152
152
|
if occurrences > 1 and replaced_count == 0:
|
153
153
|
self.report_warning(
|
154
|
-
tr(" ℹ️
|
154
|
+
tr(" ℹ️ No changes made. (not unique)"), ReportAction.CREATE
|
155
155
|
)
|
156
156
|
concise_warning = tr(
|
157
157
|
"No changes made. The search text is not unique. Expand your search context with surrounding lines to ensure uniqueness."
|
@@ -189,7 +189,7 @@ class ReplaceTextInFileTool(ToolBase):
|
|
189
189
|
action,
|
190
190
|
search_text,
|
191
191
|
replacement_text,
|
192
|
-
|
192
|
+
path,
|
193
193
|
):
|
194
194
|
"""Format the info message for the operation."""
|
195
195
|
if replace_lines == 0:
|
@@ -201,7 +201,7 @@ class ReplaceTextInFileTool(ToolBase):
|
|
201
201
|
)
|
202
202
|
else:
|
203
203
|
try:
|
204
|
-
with open(
|
204
|
+
with open(path, "r", encoding="utf-8", errors="replace") as f:
|
205
205
|
_content = f.read()
|
206
206
|
_new_content = _content.replace(
|
207
207
|
search_text, replacement_text, -1 if action else 1
|
@@ -258,11 +258,11 @@ class ReplaceTextInFileTool(ToolBase):
|
|
258
258
|
details = ""
|
259
259
|
return match_info, details
|
260
260
|
|
261
|
-
def _format_final_msg(self,
|
261
|
+
def _format_final_msg(self, path, warning, match_info, details):
|
262
262
|
"""Format the final status message."""
|
263
263
|
return tr(
|
264
|
-
"Text replaced in {
|
265
|
-
|
264
|
+
"Text replaced in {path}{warning}. {match_info}{details}",
|
265
|
+
path=path,
|
266
266
|
warning=warning,
|
267
267
|
match_info=match_info,
|
268
268
|
details=details,
|
@@ -48,7 +48,7 @@ class RunBashCommandTool(ToolBase):
|
|
48
48
|
self.report_warning(tr("ℹ️ Empty command provided."), ReportAction.EXECUTE)
|
49
49
|
return tr("Warning: Empty command provided. Operation skipped.")
|
50
50
|
self.report_action(
|
51
|
-
tr("🖥️
|
51
|
+
tr("🖥️ Run bash command: {command} ...\n", command=command),
|
52
52
|
ReportAction.EXECUTE,
|
53
53
|
)
|
54
54
|
if requires_user_input:
|
@@ -51,8 +51,8 @@ def format_result(
|
|
51
51
|
lines = []
|
52
52
|
total = 0
|
53
53
|
if per_file_counts:
|
54
|
-
for
|
55
|
-
lines.append(f"{
|
54
|
+
for path, count in per_file_counts:
|
55
|
+
lines.append(f"{path}: {count}")
|
56
56
|
total += count
|
57
57
|
lines.append(f"Total matches: {total}")
|
58
58
|
if limit_reached:
|
@@ -24,7 +24,7 @@ def filter_dirs(dirs, root, gitignore_filter):
|
|
24
24
|
|
25
25
|
|
26
26
|
def process_file_count_only(
|
27
|
-
|
27
|
+
path,
|
28
28
|
per_file_counts,
|
29
29
|
pattern,
|
30
30
|
regex,
|
@@ -34,7 +34,7 @@ def process_file_count_only(
|
|
34
34
|
total_results,
|
35
35
|
):
|
36
36
|
match_count, file_limit_reached, _ = read_file_lines(
|
37
|
-
|
37
|
+
path,
|
38
38
|
pattern,
|
39
39
|
regex,
|
40
40
|
use_regex,
|
@@ -44,12 +44,12 @@ def process_file_count_only(
|
|
44
44
|
total_results + sum(count for _, count in per_file_counts),
|
45
45
|
)
|
46
46
|
if match_count > 0:
|
47
|
-
per_file_counts.append((
|
47
|
+
per_file_counts.append((path, match_count))
|
48
48
|
return file_limit_reached
|
49
49
|
|
50
50
|
|
51
51
|
def process_file_collect(
|
52
|
-
|
52
|
+
path,
|
53
53
|
dir_output,
|
54
54
|
per_file_counts,
|
55
55
|
pattern,
|
@@ -60,7 +60,7 @@ def process_file_collect(
|
|
60
60
|
total_results,
|
61
61
|
):
|
62
62
|
actual_match_count, file_limit_reached, file_lines_output = read_file_lines(
|
63
|
-
|
63
|
+
path,
|
64
64
|
pattern,
|
65
65
|
regex,
|
66
66
|
use_regex,
|
@@ -71,7 +71,7 @@ def process_file_collect(
|
|
71
71
|
)
|
72
72
|
dir_output.extend(file_lines_output)
|
73
73
|
if actual_match_count > 0:
|
74
|
-
per_file_counts.append((
|
74
|
+
per_file_counts.append((path, actual_match_count))
|
75
75
|
return file_limit_reached
|
76
76
|
|
77
77
|
|
@@ -104,12 +104,12 @@ def traverse_directory(
|
|
104
104
|
for root, dirs, files in walker:
|
105
105
|
dirs[:] = filter_dirs(dirs, root, gitignore_filter)
|
106
106
|
for file in files:
|
107
|
-
|
108
|
-
if gitignore_filter.is_ignored(
|
107
|
+
path = os.path.join(root, file)
|
108
|
+
if gitignore_filter.is_ignored(path):
|
109
109
|
continue
|
110
110
|
if count_only:
|
111
111
|
file_limit_reached = process_file_count_only(
|
112
|
-
|
112
|
+
path,
|
113
113
|
per_file_counts,
|
114
114
|
pattern,
|
115
115
|
regex,
|
@@ -123,7 +123,7 @@ def traverse_directory(
|
|
123
123
|
break
|
124
124
|
else:
|
125
125
|
file_limit_reached = process_file_collect(
|
126
|
-
|
126
|
+
path,
|
127
127
|
dir_output,
|
128
128
|
per_file_counts,
|
129
129
|
pattern,
|
@@ -44,13 +44,13 @@ def _handle_validation_error(e, report_warning):
|
|
44
44
|
|
45
45
|
|
46
46
|
def validate_file_syntax(
|
47
|
-
|
47
|
+
path: str, report_info=None, report_warning=None, report_success=None
|
48
48
|
) -> str:
|
49
|
-
ext = os.path.splitext(
|
49
|
+
ext = os.path.splitext(path)[1].lower()
|
50
50
|
validator = _get_validator(ext)
|
51
51
|
try:
|
52
52
|
if validator:
|
53
|
-
return validator(
|
53
|
+
return validator(path)
|
54
54
|
else:
|
55
55
|
msg = tr("⚠️ Warning: Unsupported file extension: {ext}", ext=ext)
|
56
56
|
if report_warning:
|
@@ -75,7 +75,7 @@ class ValidateFileSyntaxTool(ToolBase):
|
|
75
75
|
- JavaScript (.js)
|
76
76
|
|
77
77
|
Args:
|
78
|
-
|
78
|
+
path (str): Path to the file to validate.
|
79
79
|
Returns:
|
80
80
|
str: Validation status message. Example:
|
81
81
|
- "✅ Syntax OK"
|
@@ -85,8 +85,8 @@ class ValidateFileSyntaxTool(ToolBase):
|
|
85
85
|
permissions = ToolPermissions(read=True)
|
86
86
|
tool_name = "validate_file_syntax"
|
87
87
|
|
88
|
-
def run(self,
|
89
|
-
disp_path = display_path(
|
88
|
+
def run(self, path: str) -> str:
|
89
|
+
disp_path = display_path(path)
|
90
90
|
self.report_action(
|
91
91
|
tr(
|
92
92
|
"🔎 Validate syntax for file '{disp_path}' ...",
|
@@ -95,7 +95,7 @@ class ValidateFileSyntaxTool(ToolBase):
|
|
95
95
|
ReportAction.READ,
|
96
96
|
)
|
97
97
|
result = validate_file_syntax(
|
98
|
-
|
98
|
+
path,
|
99
99
|
|
100
100
|
report_warning=self.report_warning,
|
101
101
|
report_success=self.report_success,
|
@@ -2,8 +2,8 @@ from janito.i18n import tr
|
|
2
2
|
import re
|
3
3
|
|
4
4
|
|
5
|
-
def validate_css(
|
6
|
-
with open(
|
5
|
+
def validate_css(path: str) -> str:
|
6
|
+
with open(path, "r", encoding="utf-8") as f:
|
7
7
|
content = f.read()
|
8
8
|
errors = []
|
9
9
|
# Check for unmatched curly braces
|
@@ -3,16 +3,16 @@ import re
|
|
3
3
|
from lxml import etree
|
4
4
|
|
5
5
|
|
6
|
-
def validate_html(
|
7
|
-
html_content = _read_html_content(
|
6
|
+
def validate_html(path: str) -> str:
|
7
|
+
html_content = _read_html_content(path)
|
8
8
|
warnings = _find_js_outside_script(html_content)
|
9
|
-
lxml_error = _parse_html_and_collect_errors(
|
9
|
+
lxml_error = _parse_html_and_collect_errors(path)
|
10
10
|
msg = _build_result_message(warnings, lxml_error)
|
11
11
|
return msg
|
12
12
|
|
13
13
|
|
14
|
-
def _read_html_content(
|
15
|
-
with open(
|
14
|
+
def _read_html_content(path):
|
15
|
+
with open(path, "r", encoding="utf-8") as f:
|
16
16
|
return f.read()
|
17
17
|
|
18
18
|
|
@@ -46,11 +46,11 @@ def _find_js_outside_script(html_content):
|
|
46
46
|
return warnings
|
47
47
|
|
48
48
|
|
49
|
-
def _parse_html_and_collect_errors(
|
49
|
+
def _parse_html_and_collect_errors(path):
|
50
50
|
lxml_error = None
|
51
51
|
try:
|
52
52
|
parser = etree.HTMLParser(recover=False)
|
53
|
-
with open(
|
53
|
+
with open(path, "rb") as f:
|
54
54
|
etree.parse(f, parser=parser)
|
55
55
|
error_log = parser.error_log
|
56
56
|
syntax_errors = []
|
@@ -2,8 +2,8 @@ from janito.i18n import tr
|
|
2
2
|
import re
|
3
3
|
|
4
4
|
|
5
|
-
def validate_js(
|
6
|
-
with open(
|
5
|
+
def validate_js(path: str) -> str:
|
6
|
+
with open(path, "r", encoding="utf-8") as f:
|
7
7
|
content = f.read()
|
8
8
|
errors = []
|
9
9
|
if content.count("{") != content.count("}"):
|
@@ -2,8 +2,8 @@ from janito.i18n import tr
|
|
2
2
|
import re
|
3
3
|
|
4
4
|
|
5
|
-
def validate_markdown(
|
6
|
-
with open(
|
5
|
+
def validate_markdown(path: str) -> str:
|
6
|
+
with open(path, "r", encoding="utf-8") as f:
|
7
7
|
content = f.read()
|
8
8
|
lines = content.splitlines()
|
9
9
|
errors = []
|
@@ -2,8 +2,8 @@ from janito.i18n import tr
|
|
2
2
|
import re
|
3
3
|
|
4
4
|
|
5
|
-
def validate_ps1(
|
6
|
-
with open(
|
5
|
+
def validate_ps1(path: str) -> str:
|
6
|
+
with open(path, "r", encoding="utf-8") as f:
|
7
7
|
content = f.read()
|
8
8
|
errors = []
|
9
9
|
# Unmatched curly braces
|
@@ -1,11 +1,11 @@
|
|
1
1
|
from janito.i18n import tr
|
2
2
|
|
3
3
|
|
4
|
-
def validate_xml(
|
4
|
+
def validate_xml(path: str) -> str:
|
5
5
|
try:
|
6
6
|
from lxml import etree
|
7
7
|
except ImportError:
|
8
8
|
return tr("⚠️ lxml not installed. Cannot validate XML.")
|
9
|
-
with open(
|
9
|
+
with open(path, "rb") as f:
|
10
10
|
etree.parse(f)
|
11
11
|
return "✅ OK"
|