janito 2.3.0__py3-none-any.whl → 2.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- janito/__init__.py +6 -6
- janito/cli/chat_mode/shell/autocomplete.py +21 -21
- janito/cli/chat_mode/shell/commands/clear.py +12 -12
- janito/cli/chat_mode/shell/commands/multi.py +51 -51
- janito/cli/chat_mode/shell/input_history.py +62 -62
- janito/cli/cli_commands/list_models.py +35 -35
- janito/cli/cli_commands/list_providers.py +9 -9
- janito/cli/cli_commands/list_tools.py +53 -53
- janito/cli/cli_commands/model_selection.py +50 -50
- janito/cli/cli_commands/model_utils.py +95 -95
- janito/cli/cli_commands/set_api_key.py +19 -19
- janito/cli/cli_commands/show_config.py +51 -51
- janito/cli/cli_commands/show_system_prompt.py +62 -62
- janito/cli/core/__init__.py +4 -4
- janito/cli/core/event_logger.py +59 -59
- janito/cli/core/getters.py +33 -33
- janito/cli/core/unsetters.py +54 -54
- janito/cli/single_shot_mode/__init__.py +6 -6
- janito/config.py +5 -5
- janito/config_manager.py +112 -112
- janito/drivers/anthropic/driver.py +113 -113
- janito/formatting_token.py +54 -54
- janito/i18n/__init__.py +35 -35
- janito/i18n/messages.py +23 -23
- janito/i18n/pt.py +47 -47
- janito/llm/__init__.py +5 -5
- janito/llm/agent.py +443 -443
- janito/llm/auth.py +63 -63
- janito/llm/driver_config_builder.py +34 -34
- janito/llm/driver_input.py +12 -12
- janito/llm/message_parts.py +60 -60
- janito/llm/model.py +38 -38
- janito/llm/provider.py +196 -196
- janito/provider_registry.py +176 -176
- janito/providers/anthropic/model_info.py +22 -22
- janito/providers/anthropic/provider.py +2 -0
- janito/providers/azure_openai/model_info.py +16 -16
- janito/providers/azure_openai/provider.py +3 -0
- janito/providers/deepseek/__init__.py +1 -1
- janito/providers/deepseek/model_info.py +16 -16
- janito/providers/deepseek/provider.py +94 -91
- janito/providers/google/provider.py +3 -0
- janito/providers/mistralai/provider.py +3 -0
- janito/providers/openai/provider.py +4 -0
- janito/tools/adapters/__init__.py +1 -1
- janito/tools/adapters/local/ask_user.py +102 -102
- janito/tools/adapters/local/copy_file.py +84 -84
- janito/tools/adapters/local/create_directory.py +69 -69
- janito/tools/adapters/local/create_file.py +82 -82
- janito/tools/adapters/local/fetch_url.py +97 -97
- janito/tools/adapters/local/find_files.py +138 -138
- janito/tools/adapters/local/get_file_outline/__init__.py +1 -1
- janito/tools/adapters/local/get_file_outline/core.py +117 -117
- janito/tools/adapters/local/get_file_outline/java_outline.py +40 -40
- janito/tools/adapters/local/get_file_outline/markdown_outline.py +14 -14
- janito/tools/adapters/local/get_file_outline/python_outline.py +303 -303
- janito/tools/adapters/local/get_file_outline/python_outline_v2.py +156 -156
- janito/tools/adapters/local/get_file_outline/search_outline.py +33 -33
- janito/tools/adapters/local/python_code_run.py +166 -166
- janito/tools/adapters/local/python_command_run.py +164 -164
- janito/tools/adapters/local/python_file_run.py +163 -163
- janito/tools/adapters/local/run_bash_command.py +176 -176
- janito/tools/adapters/local/run_powershell_command.py +219 -219
- janito/tools/adapters/local/search_text/__init__.py +1 -1
- janito/tools/adapters/local/search_text/core.py +201 -201
- janito/tools/adapters/local/search_text/pattern_utils.py +73 -73
- janito/tools/adapters/local/search_text/traverse_directory.py +145 -145
- janito/tools/adapters/local/validate_file_syntax/__init__.py +1 -1
- janito/tools/adapters/local/validate_file_syntax/core.py +106 -106
- janito/tools/adapters/local/validate_file_syntax/css_validator.py +35 -35
- janito/tools/adapters/local/validate_file_syntax/html_validator.py +93 -93
- janito/tools/adapters/local/validate_file_syntax/js_validator.py +27 -27
- janito/tools/adapters/local/validate_file_syntax/json_validator.py +6 -6
- janito/tools/adapters/local/validate_file_syntax/markdown_validator.py +109 -109
- janito/tools/adapters/local/validate_file_syntax/ps1_validator.py +32 -32
- janito/tools/adapters/local/validate_file_syntax/python_validator.py +5 -5
- janito/tools/adapters/local/validate_file_syntax/xml_validator.py +11 -11
- janito/tools/adapters/local/validate_file_syntax/yaml_validator.py +6 -6
- janito/tools/adapters/local/view_file.py +167 -167
- janito/tools/inspect_registry.py +17 -17
- janito/tools/tool_base.py +105 -105
- janito/tools/tool_events.py +58 -58
- janito/tools/tool_run_exception.py +12 -12
- janito/tools/tool_use_tracker.py +81 -81
- janito/tools/tool_utils.py +45 -45
- janito/tools/tools_schema.py +104 -104
- janito/version.py +4 -4
- {janito-2.3.0.dist-info → janito-2.3.1.dist-info}/METADATA +390 -388
- {janito-2.3.0.dist-info → janito-2.3.1.dist-info}/RECORD +93 -93
- {janito-2.3.0.dist-info → janito-2.3.1.dist-info}/WHEEL +0 -0
- {janito-2.3.0.dist-info → janito-2.3.1.dist-info}/entry_points.txt +0 -0
- {janito-2.3.0.dist-info → janito-2.3.1.dist-info}/licenses/LICENSE +0 -0
- {janito-2.3.0.dist-info → janito-2.3.1.dist-info}/top_level.txt +0 -0
@@ -1,201 +1,201 @@
|
|
1
|
-
from janito.tools.tool_base import ToolBase
|
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, display_path
|
5
|
-
from janito.i18n import tr
|
6
|
-
import os
|
7
|
-
from .pattern_utils import prepare_pattern, format_result, summarize_total
|
8
|
-
from .match_lines import read_file_lines
|
9
|
-
from .traverse_directory import traverse_directory
|
10
|
-
|
11
|
-
|
12
|
-
from janito.tools.adapters.local.adapter import register_local_tool as register_tool
|
13
|
-
|
14
|
-
|
15
|
-
@register_tool
|
16
|
-
class SearchTextTool(ToolBase):
|
17
|
-
"""
|
18
|
-
Search for a text pattern (regex or plain string) in all files within one or more directories or file paths and return matching lines or counts. Respects .gitignore.
|
19
|
-
Args:
|
20
|
-
paths (str): String of one or more paths (space-separated) to search in. Each path can be a directory or a file.
|
21
|
-
pattern (str): Regex pattern or plain text substring to search for in files. Must not be empty. Tries regex first, falls back to substring if regex is invalid.
|
22
|
-
is_regex (bool): If True, treat pattern as a regular expression. If False, treat as plain text (default).
|
23
|
-
case_sensitive (bool): If False, perform a case-insensitive search. Default is True (case sensitive).
|
24
|
-
max_depth (int, optional): Maximum directory depth to search. If 0 (default), search is recursive with no depth limit. If >0, limits recursion to that depth. Setting max_depth=1 disables recursion (only top-level directory). Ignored for file paths.
|
25
|
-
max_results (int, optional): Maximum number of results to return. Defaults to 100. 0 means no limit.
|
26
|
-
count_only (bool): If True, return only the count of matches per file and total, not the matching lines. Default is False.
|
27
|
-
Returns:
|
28
|
-
str: If count_only is False, matching lines from files as a newline-separated string, each formatted as 'filepath:lineno: line'.
|
29
|
-
If count_only is True, returns per-file and total match counts.
|
30
|
-
If max_results is reached, appends a note to the output.
|
31
|
-
"""
|
32
|
-
|
33
|
-
tool_name = "search_text"
|
34
|
-
|
35
|
-
def _handle_file(
|
36
|
-
self,
|
37
|
-
search_path,
|
38
|
-
pattern,
|
39
|
-
regex,
|
40
|
-
use_regex,
|
41
|
-
case_sensitive,
|
42
|
-
max_results,
|
43
|
-
total_results,
|
44
|
-
count_only,
|
45
|
-
):
|
46
|
-
if count_only:
|
47
|
-
match_count, dir_limit_reached, _ = read_file_lines(
|
48
|
-
search_path,
|
49
|
-
pattern,
|
50
|
-
regex,
|
51
|
-
use_regex,
|
52
|
-
case_sensitive,
|
53
|
-
True,
|
54
|
-
max_results,
|
55
|
-
total_results,
|
56
|
-
)
|
57
|
-
per_file_counts = [(search_path, match_count)] if match_count > 0 else []
|
58
|
-
return [], dir_limit_reached, per_file_counts
|
59
|
-
else:
|
60
|
-
dir_output, dir_limit_reached, match_count_list = read_file_lines(
|
61
|
-
search_path,
|
62
|
-
pattern,
|
63
|
-
regex,
|
64
|
-
use_regex,
|
65
|
-
case_sensitive,
|
66
|
-
False,
|
67
|
-
max_results,
|
68
|
-
total_results,
|
69
|
-
)
|
70
|
-
per_file_counts = (
|
71
|
-
[(search_path, len(match_count_list))]
|
72
|
-
if match_count_list and len(match_count_list) > 0
|
73
|
-
else []
|
74
|
-
)
|
75
|
-
return dir_output, dir_limit_reached, per_file_counts
|
76
|
-
|
77
|
-
def _handle_path(
|
78
|
-
self,
|
79
|
-
search_path,
|
80
|
-
pattern,
|
81
|
-
regex,
|
82
|
-
use_regex,
|
83
|
-
case_sensitive,
|
84
|
-
max_depth,
|
85
|
-
max_results,
|
86
|
-
total_results,
|
87
|
-
count_only,
|
88
|
-
):
|
89
|
-
info_str = tr(
|
90
|
-
"🔍 Search {search_type} '{pattern}' in '{disp_path}'",
|
91
|
-
search_type=("regex" if use_regex else "text"),
|
92
|
-
pattern=pattern,
|
93
|
-
disp_path=display_path(search_path),
|
94
|
-
)
|
95
|
-
if max_depth > 0:
|
96
|
-
info_str += tr(" [max_depth={max_depth}]", max_depth=max_depth)
|
97
|
-
if count_only:
|
98
|
-
info_str += " [count]"
|
99
|
-
self.report_action(info_str, ReportAction.READ)
|
100
|
-
if os.path.isfile(search_path):
|
101
|
-
dir_output, dir_limit_reached, per_file_counts = self._handle_file(
|
102
|
-
search_path,
|
103
|
-
pattern,
|
104
|
-
regex,
|
105
|
-
use_regex,
|
106
|
-
case_sensitive,
|
107
|
-
max_results,
|
108
|
-
total_results,
|
109
|
-
count_only,
|
110
|
-
)
|
111
|
-
else:
|
112
|
-
if count_only:
|
113
|
-
per_file_counts, dir_limit_reached, _ = traverse_directory(
|
114
|
-
search_path,
|
115
|
-
pattern,
|
116
|
-
regex,
|
117
|
-
use_regex,
|
118
|
-
case_sensitive,
|
119
|
-
max_depth,
|
120
|
-
max_results,
|
121
|
-
total_results,
|
122
|
-
True,
|
123
|
-
)
|
124
|
-
dir_output = []
|
125
|
-
else:
|
126
|
-
dir_output, dir_limit_reached, per_file_counts = traverse_directory(
|
127
|
-
search_path,
|
128
|
-
pattern,
|
129
|
-
regex,
|
130
|
-
use_regex,
|
131
|
-
case_sensitive,
|
132
|
-
max_depth,
|
133
|
-
max_results,
|
134
|
-
total_results,
|
135
|
-
False,
|
136
|
-
)
|
137
|
-
count = sum(count for _, count in per_file_counts)
|
138
|
-
file_word = pluralize("match", count)
|
139
|
-
num_files = len(per_file_counts)
|
140
|
-
file_label = pluralize("file", num_files)
|
141
|
-
file_word_max = file_word + (" (max)" if dir_limit_reached else "")
|
142
|
-
self.report_success(
|
143
|
-
tr(
|
144
|
-
" ✅ {count} {file_word} from {num_files} {file_label}",
|
145
|
-
count=count,
|
146
|
-
file_word=file_word_max,
|
147
|
-
num_files=num_files,
|
148
|
-
file_label=file_label,
|
149
|
-
),
|
150
|
-
ReportAction.READ,
|
151
|
-
)
|
152
|
-
return info_str, dir_output, dir_limit_reached, per_file_counts
|
153
|
-
|
154
|
-
def run(
|
155
|
-
self,
|
156
|
-
paths: str,
|
157
|
-
pattern: str,
|
158
|
-
is_regex: bool = False,
|
159
|
-
case_sensitive: bool = False,
|
160
|
-
max_depth: int = 0,
|
161
|
-
max_results: int = 100,
|
162
|
-
count_only: bool = False,
|
163
|
-
) -> str:
|
164
|
-
regex, use_regex, error_msg = prepare_pattern(
|
165
|
-
pattern, is_regex, case_sensitive, self.report_error, self.report_warning
|
166
|
-
)
|
167
|
-
if error_msg:
|
168
|
-
return error_msg
|
169
|
-
paths_list = paths.split()
|
170
|
-
results = []
|
171
|
-
all_per_file_counts = []
|
172
|
-
for search_path in paths_list:
|
173
|
-
info_str, dir_output, dir_limit_reached, per_file_counts = (
|
174
|
-
self._handle_path(
|
175
|
-
search_path,
|
176
|
-
pattern,
|
177
|
-
regex,
|
178
|
-
use_regex,
|
179
|
-
case_sensitive,
|
180
|
-
max_depth,
|
181
|
-
max_results,
|
182
|
-
0,
|
183
|
-
count_only,
|
184
|
-
)
|
185
|
-
)
|
186
|
-
if count_only:
|
187
|
-
all_per_file_counts.extend(per_file_counts)
|
188
|
-
result_str = format_result(
|
189
|
-
pattern,
|
190
|
-
use_regex,
|
191
|
-
dir_output,
|
192
|
-
dir_limit_reached,
|
193
|
-
count_only,
|
194
|
-
per_file_counts,
|
195
|
-
)
|
196
|
-
results.append(info_str + "\n" + result_str)
|
197
|
-
if dir_limit_reached:
|
198
|
-
break
|
199
|
-
if count_only:
|
200
|
-
results.append(summarize_total(all_per_file_counts))
|
201
|
-
return "\n\n".join(results)
|
1
|
+
from janito.tools.tool_base import ToolBase
|
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, display_path
|
5
|
+
from janito.i18n import tr
|
6
|
+
import os
|
7
|
+
from .pattern_utils import prepare_pattern, format_result, summarize_total
|
8
|
+
from .match_lines import read_file_lines
|
9
|
+
from .traverse_directory import traverse_directory
|
10
|
+
|
11
|
+
|
12
|
+
from janito.tools.adapters.local.adapter import register_local_tool as register_tool
|
13
|
+
|
14
|
+
|
15
|
+
@register_tool
|
16
|
+
class SearchTextTool(ToolBase):
|
17
|
+
"""
|
18
|
+
Search for a text pattern (regex or plain string) in all files within one or more directories or file paths and return matching lines or counts. Respects .gitignore.
|
19
|
+
Args:
|
20
|
+
paths (str): String of one or more paths (space-separated) to search in. Each path can be a directory or a file.
|
21
|
+
pattern (str): Regex pattern or plain text substring to search for in files. Must not be empty. Tries regex first, falls back to substring if regex is invalid.
|
22
|
+
is_regex (bool): If True, treat pattern as a regular expression. If False, treat as plain text (default).
|
23
|
+
case_sensitive (bool): If False, perform a case-insensitive search. Default is True (case sensitive).
|
24
|
+
max_depth (int, optional): Maximum directory depth to search. If 0 (default), search is recursive with no depth limit. If >0, limits recursion to that depth. Setting max_depth=1 disables recursion (only top-level directory). Ignored for file paths.
|
25
|
+
max_results (int, optional): Maximum number of results to return. Defaults to 100. 0 means no limit.
|
26
|
+
count_only (bool): If True, return only the count of matches per file and total, not the matching lines. Default is False.
|
27
|
+
Returns:
|
28
|
+
str: If count_only is False, matching lines from files as a newline-separated string, each formatted as 'filepath:lineno: line'.
|
29
|
+
If count_only is True, returns per-file and total match counts.
|
30
|
+
If max_results is reached, appends a note to the output.
|
31
|
+
"""
|
32
|
+
|
33
|
+
tool_name = "search_text"
|
34
|
+
|
35
|
+
def _handle_file(
|
36
|
+
self,
|
37
|
+
search_path,
|
38
|
+
pattern,
|
39
|
+
regex,
|
40
|
+
use_regex,
|
41
|
+
case_sensitive,
|
42
|
+
max_results,
|
43
|
+
total_results,
|
44
|
+
count_only,
|
45
|
+
):
|
46
|
+
if count_only:
|
47
|
+
match_count, dir_limit_reached, _ = read_file_lines(
|
48
|
+
search_path,
|
49
|
+
pattern,
|
50
|
+
regex,
|
51
|
+
use_regex,
|
52
|
+
case_sensitive,
|
53
|
+
True,
|
54
|
+
max_results,
|
55
|
+
total_results,
|
56
|
+
)
|
57
|
+
per_file_counts = [(search_path, match_count)] if match_count > 0 else []
|
58
|
+
return [], dir_limit_reached, per_file_counts
|
59
|
+
else:
|
60
|
+
dir_output, dir_limit_reached, match_count_list = read_file_lines(
|
61
|
+
search_path,
|
62
|
+
pattern,
|
63
|
+
regex,
|
64
|
+
use_regex,
|
65
|
+
case_sensitive,
|
66
|
+
False,
|
67
|
+
max_results,
|
68
|
+
total_results,
|
69
|
+
)
|
70
|
+
per_file_counts = (
|
71
|
+
[(search_path, len(match_count_list))]
|
72
|
+
if match_count_list and len(match_count_list) > 0
|
73
|
+
else []
|
74
|
+
)
|
75
|
+
return dir_output, dir_limit_reached, per_file_counts
|
76
|
+
|
77
|
+
def _handle_path(
|
78
|
+
self,
|
79
|
+
search_path,
|
80
|
+
pattern,
|
81
|
+
regex,
|
82
|
+
use_regex,
|
83
|
+
case_sensitive,
|
84
|
+
max_depth,
|
85
|
+
max_results,
|
86
|
+
total_results,
|
87
|
+
count_only,
|
88
|
+
):
|
89
|
+
info_str = tr(
|
90
|
+
"🔍 Search {search_type} '{pattern}' in '{disp_path}'",
|
91
|
+
search_type=("regex" if use_regex else "text"),
|
92
|
+
pattern=pattern,
|
93
|
+
disp_path=display_path(search_path),
|
94
|
+
)
|
95
|
+
if max_depth > 0:
|
96
|
+
info_str += tr(" [max_depth={max_depth}]", max_depth=max_depth)
|
97
|
+
if count_only:
|
98
|
+
info_str += " [count]"
|
99
|
+
self.report_action(info_str, ReportAction.READ)
|
100
|
+
if os.path.isfile(search_path):
|
101
|
+
dir_output, dir_limit_reached, per_file_counts = self._handle_file(
|
102
|
+
search_path,
|
103
|
+
pattern,
|
104
|
+
regex,
|
105
|
+
use_regex,
|
106
|
+
case_sensitive,
|
107
|
+
max_results,
|
108
|
+
total_results,
|
109
|
+
count_only,
|
110
|
+
)
|
111
|
+
else:
|
112
|
+
if count_only:
|
113
|
+
per_file_counts, dir_limit_reached, _ = traverse_directory(
|
114
|
+
search_path,
|
115
|
+
pattern,
|
116
|
+
regex,
|
117
|
+
use_regex,
|
118
|
+
case_sensitive,
|
119
|
+
max_depth,
|
120
|
+
max_results,
|
121
|
+
total_results,
|
122
|
+
True,
|
123
|
+
)
|
124
|
+
dir_output = []
|
125
|
+
else:
|
126
|
+
dir_output, dir_limit_reached, per_file_counts = traverse_directory(
|
127
|
+
search_path,
|
128
|
+
pattern,
|
129
|
+
regex,
|
130
|
+
use_regex,
|
131
|
+
case_sensitive,
|
132
|
+
max_depth,
|
133
|
+
max_results,
|
134
|
+
total_results,
|
135
|
+
False,
|
136
|
+
)
|
137
|
+
count = sum(count for _, count in per_file_counts)
|
138
|
+
file_word = pluralize("match", count)
|
139
|
+
num_files = len(per_file_counts)
|
140
|
+
file_label = pluralize("file", num_files)
|
141
|
+
file_word_max = file_word + (" (max)" if dir_limit_reached else "")
|
142
|
+
self.report_success(
|
143
|
+
tr(
|
144
|
+
" ✅ {count} {file_word} from {num_files} {file_label}",
|
145
|
+
count=count,
|
146
|
+
file_word=file_word_max,
|
147
|
+
num_files=num_files,
|
148
|
+
file_label=file_label,
|
149
|
+
),
|
150
|
+
ReportAction.READ,
|
151
|
+
)
|
152
|
+
return info_str, dir_output, dir_limit_reached, per_file_counts
|
153
|
+
|
154
|
+
def run(
|
155
|
+
self,
|
156
|
+
paths: str,
|
157
|
+
pattern: str,
|
158
|
+
is_regex: bool = False,
|
159
|
+
case_sensitive: bool = False,
|
160
|
+
max_depth: int = 0,
|
161
|
+
max_results: int = 100,
|
162
|
+
count_only: bool = False,
|
163
|
+
) -> str:
|
164
|
+
regex, use_regex, error_msg = prepare_pattern(
|
165
|
+
pattern, is_regex, case_sensitive, self.report_error, self.report_warning
|
166
|
+
)
|
167
|
+
if error_msg:
|
168
|
+
return error_msg
|
169
|
+
paths_list = paths.split()
|
170
|
+
results = []
|
171
|
+
all_per_file_counts = []
|
172
|
+
for search_path in paths_list:
|
173
|
+
info_str, dir_output, dir_limit_reached, per_file_counts = (
|
174
|
+
self._handle_path(
|
175
|
+
search_path,
|
176
|
+
pattern,
|
177
|
+
regex,
|
178
|
+
use_regex,
|
179
|
+
case_sensitive,
|
180
|
+
max_depth,
|
181
|
+
max_results,
|
182
|
+
0,
|
183
|
+
count_only,
|
184
|
+
)
|
185
|
+
)
|
186
|
+
if count_only:
|
187
|
+
all_per_file_counts.extend(per_file_counts)
|
188
|
+
result_str = format_result(
|
189
|
+
pattern,
|
190
|
+
use_regex,
|
191
|
+
dir_output,
|
192
|
+
dir_limit_reached,
|
193
|
+
count_only,
|
194
|
+
per_file_counts,
|
195
|
+
)
|
196
|
+
results.append(info_str + "\n" + result_str)
|
197
|
+
if dir_limit_reached:
|
198
|
+
break
|
199
|
+
if count_only:
|
200
|
+
results.append(summarize_total(all_per_file_counts))
|
201
|
+
return "\n\n".join(results)
|
@@ -1,73 +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
|
-
pattern, 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 file_path, count in per_file_counts:
|
55
|
-
lines.append(f"{file_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
|
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
|
+
pattern, 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 file_path, count in per_file_counts:
|
55
|
+
lines.append(f"{file_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
|