janito 2.2.0__py3-none-any.whl → 2.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.
Files changed (130) hide show
  1. janito/__init__.py +6 -6
  2. janito/agent/setup_agent.py +14 -5
  3. janito/agent/templates/profiles/system_prompt_template_main.txt.j2 +3 -1
  4. janito/cli/chat_mode/bindings.py +6 -0
  5. janito/cli/chat_mode/session.py +16 -0
  6. janito/cli/chat_mode/shell/autocomplete.py +21 -21
  7. janito/cli/chat_mode/shell/commands/__init__.py +3 -0
  8. janito/cli/chat_mode/shell/commands/clear.py +12 -12
  9. janito/cli/chat_mode/shell/commands/exec.py +27 -0
  10. janito/cli/chat_mode/shell/commands/multi.py +51 -51
  11. janito/cli/chat_mode/shell/commands/tools.py +17 -6
  12. janito/cli/chat_mode/shell/input_history.py +62 -62
  13. janito/cli/chat_mode/shell/session/manager.py +1 -0
  14. janito/cli/chat_mode/toolbar.py +1 -0
  15. janito/cli/cli_commands/list_models.py +35 -35
  16. janito/cli/cli_commands/list_providers.py +9 -9
  17. janito/cli/cli_commands/list_tools.py +53 -53
  18. janito/cli/cli_commands/model_selection.py +50 -50
  19. janito/cli/cli_commands/model_utils.py +13 -2
  20. janito/cli/cli_commands/set_api_key.py +19 -19
  21. janito/cli/cli_commands/show_config.py +51 -51
  22. janito/cli/cli_commands/show_system_prompt.py +62 -62
  23. janito/cli/config.py +2 -1
  24. janito/cli/core/__init__.py +4 -4
  25. janito/cli/core/event_logger.py +59 -59
  26. janito/cli/core/getters.py +3 -1
  27. janito/cli/core/runner.py +165 -148
  28. janito/cli/core/setters.py +5 -1
  29. janito/cli/core/unsetters.py +54 -54
  30. janito/cli/main_cli.py +12 -1
  31. janito/cli/prompt_core.py +5 -2
  32. janito/cli/rich_terminal_reporter.py +22 -3
  33. janito/cli/single_shot_mode/__init__.py +6 -6
  34. janito/cli/single_shot_mode/handler.py +11 -1
  35. janito/cli/verbose_output.py +1 -1
  36. janito/config.py +5 -5
  37. janito/config_manager.py +2 -0
  38. janito/driver_events.py +14 -0
  39. janito/drivers/anthropic/driver.py +113 -113
  40. janito/drivers/azure_openai/driver.py +38 -3
  41. janito/drivers/driver_registry.py +0 -2
  42. janito/drivers/openai/driver.py +196 -36
  43. janito/formatting_token.py +54 -54
  44. janito/i18n/__init__.py +35 -35
  45. janito/i18n/messages.py +23 -23
  46. janito/i18n/pt.py +47 -47
  47. janito/llm/__init__.py +5 -5
  48. janito/llm/agent.py +443 -443
  49. janito/llm/auth.py +1 -0
  50. janito/llm/driver.py +7 -1
  51. janito/llm/driver_config.py +1 -0
  52. janito/llm/driver_config_builder.py +34 -34
  53. janito/llm/driver_input.py +12 -12
  54. janito/llm/message_parts.py +60 -60
  55. janito/llm/model.py +38 -38
  56. janito/llm/provider.py +196 -196
  57. janito/provider_config.py +7 -3
  58. janito/provider_registry.py +176 -158
  59. janito/providers/__init__.py +1 -0
  60. janito/providers/anthropic/model_info.py +22 -22
  61. janito/providers/anthropic/provider.py +2 -2
  62. janito/providers/azure_openai/model_info.py +7 -6
  63. janito/providers/azure_openai/provider.py +30 -2
  64. janito/providers/deepseek/__init__.py +1 -1
  65. janito/providers/deepseek/model_info.py +16 -16
  66. janito/providers/deepseek/provider.py +91 -91
  67. janito/providers/google/model_info.py +21 -29
  68. janito/providers/google/provider.py +49 -38
  69. janito/providers/mistralai/provider.py +2 -2
  70. janito/providers/provider_static_info.py +2 -3
  71. janito/tools/adapters/__init__.py +1 -1
  72. janito/tools/adapters/local/adapter.py +33 -11
  73. janito/tools/adapters/local/ask_user.py +102 -102
  74. janito/tools/adapters/local/copy_file.py +84 -84
  75. janito/tools/adapters/local/create_directory.py +69 -69
  76. janito/tools/adapters/local/create_file.py +82 -82
  77. janito/tools/adapters/local/delete_text_in_file.py +4 -7
  78. janito/tools/adapters/local/fetch_url.py +97 -97
  79. janito/tools/adapters/local/find_files.py +138 -138
  80. janito/tools/adapters/local/get_file_outline/__init__.py +1 -1
  81. janito/tools/adapters/local/get_file_outline/core.py +117 -117
  82. janito/tools/adapters/local/get_file_outline/java_outline.py +40 -40
  83. janito/tools/adapters/local/get_file_outline/markdown_outline.py +14 -14
  84. janito/tools/adapters/local/get_file_outline/python_outline.py +303 -303
  85. janito/tools/adapters/local/get_file_outline/python_outline_v2.py +156 -156
  86. janito/tools/adapters/local/get_file_outline/search_outline.py +33 -33
  87. janito/tools/adapters/local/move_file.py +3 -13
  88. janito/tools/adapters/local/python_code_run.py +166 -166
  89. janito/tools/adapters/local/python_command_run.py +164 -164
  90. janito/tools/adapters/local/python_file_run.py +163 -163
  91. janito/tools/adapters/local/remove_directory.py +6 -17
  92. janito/tools/adapters/local/remove_file.py +4 -10
  93. janito/tools/adapters/local/replace_text_in_file.py +6 -9
  94. janito/tools/adapters/local/run_bash_command.py +176 -176
  95. janito/tools/adapters/local/run_powershell_command.py +219 -219
  96. janito/tools/adapters/local/search_text/__init__.py +1 -1
  97. janito/tools/adapters/local/search_text/core.py +201 -201
  98. janito/tools/adapters/local/search_text/match_lines.py +1 -1
  99. janito/tools/adapters/local/search_text/pattern_utils.py +73 -73
  100. janito/tools/adapters/local/search_text/traverse_directory.py +145 -145
  101. janito/tools/adapters/local/validate_file_syntax/__init__.py +1 -1
  102. janito/tools/adapters/local/validate_file_syntax/core.py +106 -106
  103. janito/tools/adapters/local/validate_file_syntax/css_validator.py +35 -35
  104. janito/tools/adapters/local/validate_file_syntax/html_validator.py +93 -93
  105. janito/tools/adapters/local/validate_file_syntax/js_validator.py +27 -27
  106. janito/tools/adapters/local/validate_file_syntax/json_validator.py +6 -6
  107. janito/tools/adapters/local/validate_file_syntax/markdown_validator.py +109 -109
  108. janito/tools/adapters/local/validate_file_syntax/ps1_validator.py +32 -32
  109. janito/tools/adapters/local/validate_file_syntax/python_validator.py +5 -5
  110. janito/tools/adapters/local/validate_file_syntax/xml_validator.py +11 -11
  111. janito/tools/adapters/local/validate_file_syntax/yaml_validator.py +6 -6
  112. janito/tools/adapters/local/view_file.py +167 -167
  113. janito/tools/inspect_registry.py +17 -17
  114. janito/tools/tool_base.py +105 -105
  115. janito/tools/tool_events.py +58 -58
  116. janito/tools/tool_run_exception.py +12 -12
  117. janito/tools/tool_use_tracker.py +81 -81
  118. janito/tools/tool_utils.py +45 -45
  119. janito/tools/tools_adapter.py +78 -6
  120. janito/tools/tools_schema.py +104 -104
  121. janito/version.py +4 -4
  122. {janito-2.2.0.dist-info → janito-2.3.0.dist-info}/METADATA +388 -251
  123. janito-2.3.0.dist-info/RECORD +181 -0
  124. janito/drivers/google_genai/driver.py +0 -54
  125. janito/drivers/google_genai/schema_generator.py +0 -67
  126. janito-2.2.0.dist-info/RECORD +0 -182
  127. {janito-2.2.0.dist-info → janito-2.3.0.dist-info}/WHEEL +0 -0
  128. {janito-2.2.0.dist-info → janito-2.3.0.dist-info}/entry_points.txt +0 -0
  129. {janito-2.2.0.dist-info → janito-2.3.0.dist-info}/licenses/LICENSE +0 -0
  130. {janito-2.2.0.dist-info → janito-2.3.0.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)
@@ -24,7 +24,7 @@ def match_line(line, pattern, regex, use_regex, case_sensitive):
24
24
  if use_regex:
25
25
  return regex and regex.search(line)
26
26
  if not case_sensitive:
27
- return pattern in line.lower()
27
+ return pattern.lower() in line.lower()
28
28
  return pattern in line
29
29
 
30
30
 
@@ -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