janito 0.7.0__py3-none-any.whl → 0.9.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 (114) hide show
  1. janito/__init__.py +5 -2
  2. janito/__main__.py +151 -142
  3. janito/callbacks.py +130 -0
  4. janito/cli.py +202 -0
  5. janito/config.py +57 -96
  6. janito/data/instructions.txt +6 -0
  7. janito/test_file.py +4 -0
  8. janito/token_report.py +73 -0
  9. janito/tools/__init__.py +10 -0
  10. janito/tools/decorators.py +84 -0
  11. janito/tools/delete_file.py +44 -0
  12. janito/tools/find_files.py +154 -0
  13. janito/tools/search_text.py +197 -0
  14. janito/tools/str_replace_editor/__init__.py +6 -0
  15. janito/tools/str_replace_editor/editor.py +43 -0
  16. janito/tools/str_replace_editor/handlers.py +338 -0
  17. janito/tools/str_replace_editor/utils.py +88 -0
  18. {janito-0.7.0.dist-info/licenses → janito-0.9.0.dist-info}/LICENSE +20 -20
  19. janito-0.9.0.dist-info/METADATA +9 -0
  20. janito-0.9.0.dist-info/RECORD +23 -0
  21. {janito-0.7.0.dist-info → janito-0.9.0.dist-info}/WHEEL +2 -1
  22. janito-0.9.0.dist-info/entry_points.txt +2 -0
  23. janito-0.9.0.dist-info/top_level.txt +1 -0
  24. janito/agents/__init__.py +0 -22
  25. janito/agents/agent.py +0 -28
  26. janito/agents/claudeai.py +0 -45
  27. janito/agents/openai.py +0 -57
  28. janito/agents/test.py +0 -34
  29. janito/change/__init__.py +0 -32
  30. janito/change/__main__.py +0 -0
  31. janito/change/analysis/__init__.py +0 -23
  32. janito/change/analysis/__main__.py +0 -7
  33. janito/change/analysis/analyze.py +0 -62
  34. janito/change/analysis/formatting.py +0 -78
  35. janito/change/analysis/options.py +0 -81
  36. janito/change/analysis/prompts.py +0 -90
  37. janito/change/analysis/view/__init__.py +0 -9
  38. janito/change/analysis/view/terminal.py +0 -181
  39. janito/change/applier/__init__.py +0 -5
  40. janito/change/applier/file.py +0 -58
  41. janito/change/applier/main.py +0 -156
  42. janito/change/applier/text.py +0 -247
  43. janito/change/applier/workspace_dir.py +0 -58
  44. janito/change/core.py +0 -124
  45. janito/change/history.py +0 -44
  46. janito/change/operations.py +0 -7
  47. janito/change/parser.py +0 -287
  48. janito/change/play.py +0 -54
  49. janito/change/preview.py +0 -82
  50. janito/change/prompts.py +0 -121
  51. janito/change/test.py +0 -0
  52. janito/change/validator.py +0 -269
  53. janito/change/viewer/__init__.py +0 -11
  54. janito/change/viewer/content.py +0 -66
  55. janito/change/viewer/diff.py +0 -43
  56. janito/change/viewer/panels.py +0 -533
  57. janito/change/viewer/styling.py +0 -114
  58. janito/change/viewer/themes.py +0 -55
  59. janito/clear_statement_parser/clear_statement_format.txt +0 -328
  60. janito/clear_statement_parser/examples.txt +0 -326
  61. janito/clear_statement_parser/models.py +0 -104
  62. janito/clear_statement_parser/parser.py +0 -496
  63. janito/cli/__init__.py +0 -2
  64. janito/cli/base.py +0 -30
  65. janito/cli/commands.py +0 -88
  66. janito/cli/functions.py +0 -111
  67. janito/cli/history.py +0 -61
  68. janito/cli/registry.py +0 -26
  69. janito/common.py +0 -80
  70. janito/demo/__init__.py +0 -4
  71. janito/demo/data.py +0 -13
  72. janito/demo/mock_data.py +0 -20
  73. janito/demo/operations.py +0 -45
  74. janito/demo/runner.py +0 -59
  75. janito/demo/scenarios.py +0 -32
  76. janito/prompt.py +0 -36
  77. janito/qa.py +0 -57
  78. janito/review.py +0 -13
  79. janito/search_replace/README.md +0 -192
  80. janito/search_replace/__init__.py +0 -7
  81. janito/search_replace/__main__.py +0 -21
  82. janito/search_replace/core.py +0 -120
  83. janito/search_replace/logger.py +0 -35
  84. janito/search_replace/parser.py +0 -52
  85. janito/search_replace/play.py +0 -61
  86. janito/search_replace/replacer.py +0 -36
  87. janito/search_replace/searcher.py +0 -411
  88. janito/search_replace/strategy_result.py +0 -10
  89. janito/shell/__init__.py +0 -38
  90. janito/shell/bus.py +0 -31
  91. janito/shell/commands.py +0 -136
  92. janito/shell/history.py +0 -20
  93. janito/shell/processor.py +0 -32
  94. janito/shell/prompt.py +0 -48
  95. janito/shell/registry.py +0 -60
  96. janito/tui/__init__.py +0 -21
  97. janito/tui/base.py +0 -22
  98. janito/tui/flows/__init__.py +0 -5
  99. janito/tui/flows/changes.py +0 -65
  100. janito/tui/flows/content.py +0 -128
  101. janito/tui/flows/selection.py +0 -117
  102. janito/tui/screens/__init__.py +0 -3
  103. janito/tui/screens/app.py +0 -1
  104. janito/version.py +0 -23
  105. janito/workspace/__init__.py +0 -6
  106. janito/workspace/analysis.py +0 -121
  107. janito/workspace/show.py +0 -141
  108. janito/workspace/stats.py +0 -43
  109. janito/workspace/types.py +0 -98
  110. janito/workspace/workset.py +0 -108
  111. janito/workspace/workspace.py +0 -114
  112. janito-0.7.0.dist-info/METADATA +0 -167
  113. janito-0.7.0.dist-info/RECORD +0 -96
  114. janito-0.7.0.dist-info/entry_points.txt +0 -2
@@ -1,411 +0,0 @@
1
- from typing import List, Optional, Dict, Type
2
- from abc import ABC, abstractmethod
3
- import re
4
- from .strategy_result import StrategyResult
5
-
6
- LINE_OVER_LINE_DEBUG = False
7
-
8
- class SearchStrategy(ABC):
9
- """Base class for search strategies."""
10
-
11
- def __init__(self):
12
- """Initialize strategy with name derived from class name."""
13
- self.name = self.__class__.__name__.replace('Strategy', '')
14
-
15
- @abstractmethod
16
- def match(self, source_lines: List[str], pattern_lines: List[str], pos: int, searcher: 'Searcher') -> bool:
17
- """Check if pattern matches source at given position.
18
-
19
- Args:
20
- source_lines: List of source code lines to search in
21
- pattern_lines: List of pattern lines to match
22
- pos: Position in source_lines to start matching
23
- searcher: Searcher instance for utility methods
24
-
25
- Returns:
26
- bool: True if pattern matches at position, False otherwise
27
- """
28
- pass
29
-
30
- class ExactMatchStrategy(SearchStrategy):
31
- """Strategy for exact match including indentation."""
32
-
33
- def match(self, source_lines: List[str], pattern_lines: List[str], pos: int, searcher: 'Searcher') -> bool:
34
- """Match pattern exactly with indentation.
35
-
36
- Args:
37
- source_lines: List of source code lines to search in
38
- pattern_lines: List of pattern lines to match
39
- pos: Position in source_lines to start matching
40
- searcher: Searcher instance for utility methods
41
-
42
- Returns:
43
- bool: True if pattern matches exactly at position, False otherwise
44
- """
45
- if pos + len(pattern_lines) > len(source_lines):
46
- return False
47
- return all(source_lines[pos + i] == pattern_line
48
- for i, pattern_line in enumerate(pattern_lines))
49
-
50
- class ExactContentStrategy(SearchStrategy):
51
- """Exact content match ignoring all indentation."""
52
-
53
- def match(self, source_lines: List[str], pattern_lines: List[str], pos: int, searcher: 'Searcher') -> bool:
54
- """Match pattern exactly ignoring indentation.
55
-
56
- Args:
57
- source_lines: List of source code lines to search in
58
- pattern_lines: List of pattern lines to match
59
- pos: Position in source_lines to start matching
60
- searcher: Searcher instance for utility methods
61
-
62
- Returns:
63
- bool: True if pattern matches exactly at position, False otherwise
64
- """
65
- if pos + len(pattern_lines) > len(source_lines):
66
- return False
67
- return all(source_lines[pos + i].strip() == pattern_line.strip()
68
- for i, pattern_line in enumerate(pattern_lines)
69
- if pattern_line.strip())
70
-
71
- class IndentAwareStrategy(SearchStrategy):
72
- """Indentation-aware matching preserving relative indentation."""
73
-
74
- def match(self, source_lines: List[str], pattern_lines: List[str], pos: int, searcher: 'Searcher') -> bool:
75
- """Match pattern preserving relative indentation.
76
-
77
- Args:
78
- source_lines: List of source code lines to search in
79
- pattern_lines: List of pattern lines to match
80
- pos: Position in source_lines to start matching
81
- searcher: Searcher instance for utility methods
82
-
83
- Returns:
84
- bool: True if pattern matches preserving indentation at position, False otherwise
85
- """
86
- if pos + len(pattern_lines) > len(source_lines):
87
- return False
88
- match_indent = searcher.get_indentation(source_lines[pos])
89
- return all(source_lines[pos + i].startswith(match_indent + pattern_line)
90
- for i, pattern_line in enumerate(pattern_lines)
91
- if pattern_line.strip())
92
-
93
- class ExactContentNoComments(SearchStrategy):
94
- """Exact content match ignoring indentation, comments, and empty lines."""
95
-
96
- def _strip_comments(self, line: str) -> str:
97
- """Remove comments from line."""
98
- if '#' in line:
99
- line = line.split('#')[0]
100
- if '//' in line:
101
- line = line.split('//')[0]
102
- return line.strip()
103
-
104
- def match(self, source_lines: List[str], pattern_lines: List[str], pos: int, searcher: 'Searcher') -> bool:
105
- """Match pattern ignoring comments and empty lines.
106
-
107
- Args:
108
- source_lines: List of source code lines to search in
109
- pattern_lines: List of pattern lines to match
110
- pos: Position in source_lines to start matching
111
- searcher: Searcher instance for utility methods
112
-
113
- Returns:
114
- bool: True if pattern matches ignoring comments at position, False otherwise
115
- """
116
- if pos + len(pattern_lines) > len(source_lines):
117
- return False
118
-
119
- if searcher.debug_mode and LINE_OVER_LINE_DEBUG:
120
- print("\n[DEBUG] ExactContentNoComments trying to match at line", pos + 1)
121
-
122
- # Filter out comments and empty lines from pattern
123
- pattern_content = [self._strip_comments(line) for line in pattern_lines]
124
- pattern_content = [line for line in pattern_content if line]
125
-
126
- if searcher.debug_mode and LINE_OVER_LINE_DEBUG:
127
- print("[DEBUG] Pattern after processing:")
128
- for i, line in enumerate(pattern_content):
129
- print(f"[DEBUG] {i+1}: '{line}'")
130
-
131
- # Match against source, ignoring comments and empty lines
132
- source_idx = pos
133
- pattern_idx = 0
134
-
135
- while pattern_idx < len(pattern_content) and source_idx < len(source_lines):
136
- source_line = self._strip_comments(source_lines[source_idx])
137
- if not source_line:
138
- source_idx += 1
139
- continue
140
-
141
- if searcher.debug_mode and LINE_OVER_LINE_DEBUG:
142
- print(f"[DEBUG] Line {source_idx + 1}: '{source_line}' vs '{pattern_content[pattern_idx]}'")
143
-
144
- if source_line != pattern_content[pattern_idx]:
145
- if searcher.debug_mode and LINE_OVER_LINE_DEBUG:
146
- print("[DEBUG] Line mismatch")
147
- return False
148
-
149
- pattern_idx += 1
150
- source_idx += 1
151
-
152
- match_result = pattern_idx == len(pattern_content)
153
- if match_result and searcher.debug_mode:
154
- print("[DEBUG] Match found")
155
- return True
156
-
157
- return False
158
-
159
- class ExactContentNoCommentsFirstLinePartial(SearchStrategy):
160
- """Match first line partially, ignoring comments."""
161
-
162
- def _strip_comments(self, line: str) -> str:
163
- """Remove comments from line."""
164
- if '#' in line:
165
- line = line.split('#')[0]
166
- if '//' in line:
167
- line = line.split('//')[0]
168
- return line.strip()
169
-
170
- def match(self, source_lines: List[str], pattern_lines: List[str], pos: int, searcher: 'Searcher') -> bool:
171
- """Match first line of pattern partially ignoring comments.
172
-
173
- Args:
174
- source_lines: List of source code lines to search in
175
- pattern_lines: List of pattern lines to match
176
- pos: Position in source_lines to start matching
177
- searcher: Searcher instance for utility methods
178
-
179
- Returns:
180
- bool: True if first line of pattern matches partially at position, False otherwise
181
- """
182
- if pos >= len(source_lines):
183
- return False
184
-
185
- # Get first non-empty pattern line
186
- pattern_content = []
187
- for line in pattern_lines:
188
- stripped = self._strip_comments(line)
189
- if stripped:
190
- pattern_content.append(stripped)
191
- break
192
-
193
- if not pattern_content:
194
- return False
195
-
196
- # Get source line content
197
- source_line = self._strip_comments(source_lines[pos])
198
- if not source_line:
199
- return False
200
-
201
- # Check if pattern content is part of source line
202
- return pattern_content[0] in source_line
203
-
204
- class Searcher:
205
- """Handles pattern searching in source code with configurable strategies."""
206
-
207
- def __init__(self, debug: bool = False):
208
- """Initialize searcher with debug mode."""
209
- self.debug_mode = debug
210
-
211
- @classmethod
212
- def set_debug(cls, enabled: bool):
213
- """Enable or disable debug mode - deprecated, use instance property instead"""
214
- # Remove the class-level debug setting as it's no longer needed
215
- raise DeprecationWarning("Class-level debug setting is deprecated. Use instance debug_mode property instead.")
216
-
217
- # Updated extension to strategy mapping to include ExactContentNoComments
218
- EXTENSION_STRATEGIES = {
219
- '.py': [ExactMatchStrategy(), IndentAwareStrategy(), ExactContentStrategy(), ExactContentNoComments(), ExactContentNoCommentsFirstLinePartial()],
220
- '.java': [ExactMatchStrategy(), IndentAwareStrategy(), ExactContentStrategy(), ExactContentNoComments(), ExactContentNoCommentsFirstLinePartial()],
221
- '.js': [ExactMatchStrategy(), IndentAwareStrategy(), ExactContentStrategy(), ExactContentNoComments(), ExactContentNoCommentsFirstLinePartial()],
222
- '.ts': [ExactMatchStrategy(), IndentAwareStrategy(), ExactContentStrategy(), ExactContentNoComments(), ExactContentNoCommentsFirstLinePartial()],
223
- '*': [ExactMatchStrategy(), ExactContentStrategy(), ExactContentNoComments(), ExactContentNoCommentsFirstLinePartial()] # updated default fallback
224
- }
225
-
226
- def get_strategies(self, file_ext: Optional[str]) -> List[SearchStrategy]:
227
- """Get search strategies for given file extension."""
228
- if not file_ext:
229
- return self.EXTENSION_STRATEGIES['*']
230
- return self.EXTENSION_STRATEGIES.get(file_ext.lower(), self.EXTENSION_STRATEGIES['*'])
231
-
232
- @staticmethod
233
- def get_indentation(line: str) -> str:
234
- """Get the leading whitespace of a line."""
235
- return re.match(r'^[ \t]*', line).group()
236
-
237
- @staticmethod
238
- def get_first_non_empty_line(text: str) -> tuple[str, int]:
239
- """Get first non-empty line and its index."""
240
- lines = text.splitlines()
241
- for i, line in enumerate(lines):
242
- if line.strip():
243
- return line, i
244
- return '', 0
245
-
246
- @staticmethod
247
- def get_last_non_empty_line(text: str) -> tuple[str, int]:
248
- """Get last non-empty line and its index."""
249
- lines = text.splitlines()
250
- for i in range(len(lines) - 1, -1, -1):
251
- if lines[i].strip():
252
- return lines[i], i
253
- return '', 0
254
-
255
- def _build_indent_map(self, text: str) -> dict[int, int]:
256
- """Build a map of line numbers to their indentation levels.
257
-
258
- Args:
259
- text: Source text to analyze
260
-
261
- Returns:
262
- dict[int, int]: Mapping of line numbers to indentation levels
263
- """
264
- indent_map = {}
265
- for i, line in enumerate(text.splitlines()):
266
- if line.strip(): # Only track non-empty lines
267
- indent_map[i] = len(self.get_indentation(line))
268
- if self.debug_mode:
269
- print(f"[DEBUG] Line {i}: Indentation level {indent_map[i]}")
270
- return indent_map
271
-
272
- def normalize_pattern(self, pattern: str, base_indent: str = '') -> str:
273
- """Remove base indentation from pattern to help with matching."""
274
- lines = pattern.splitlines()
275
- first_line, start_idx = self.get_first_non_empty_line(pattern)
276
- last_line, end_idx = self.get_last_non_empty_line(pattern)
277
-
278
- # Calculate minimum indentation from first and last non-empty lines
279
- first_indent = len(self.get_indentation(first_line))
280
- last_indent = len(self.get_indentation(last_line))
281
- min_indent = min(first_indent, last_indent)
282
-
283
- if self.debug_mode:
284
- print(f"[DEBUG] First line indent: {first_indent}")
285
- print(f"[DEBUG] Last line indent: {last_indent}")
286
- print(f"[DEBUG] Using minimum indent: {min_indent}")
287
-
288
- normalized = []
289
- for i, line in enumerate(lines):
290
- if not line.strip():
291
- normalized.append('')
292
- else:
293
- line_indent = len(self.get_indentation(line))
294
- if line_indent < min_indent:
295
- if self.debug_mode:
296
- print(f"[DEBUG] Warning: Line {i} has less indentation ({line_indent}) than minimum ({min_indent})")
297
- normalized.append(line)
298
- else:
299
- normalized.append(line[min_indent:])
300
- if self.debug_mode:
301
- print(f"[DEBUG] Normalized line {i}: '{normalized[-1]}'")
302
-
303
- return '\n'.join(normalized)
304
-
305
- def _find_best_match_position(self, positions: List[int], source_lines: List[str], pattern_base_indent: int) -> Optional[int]:
306
- """Find the best matching position among candidates.
307
-
308
- Args:
309
- positions: List of candidate line positions
310
- source_lines: List of source code lines
311
- pattern_base_indent: Base indentation level of pattern
312
-
313
- Returns:
314
- Optional[int]: Best matching position or None if no matches
315
- """
316
- if self.debug_mode:
317
- print(f"[DEBUG] Finding best match among positions: {[p+1 for p in positions]}") # Show 1-based line numbers
318
-
319
- if not positions:
320
- return None
321
-
322
- best_pos = min(positions) # Simply take the earliest match
323
- if self.debug_mode:
324
- print(f"[DEBUG] Selected match at line {best_pos + 1}") # Show 1-based line number
325
- return best_pos
326
-
327
- def try_match_with_strategies(self, source_lines: List[str], pattern_lines: List[str],
328
- pos: int, strategies: List[SearchStrategy]) -> StrategyResult:
329
- """Try matching using multiple strategies in sequence.
330
-
331
- Args:
332
- source_lines: List of source code lines
333
- pattern_lines: List of pattern lines to match
334
- pos: Position to start matching
335
- strategies: List of strategies to try
336
-
337
- Returns:
338
- StrategyResult: Result containing match success and strategy used
339
- """
340
- if self.debug_mode and LINE_OVER_LINE_DEBUG:
341
- print(f"\n[DEBUG] Trying to match at line {pos + 1}")
342
-
343
- for strategy in strategies:
344
- if strategy.match(source_lines, pattern_lines, pos, self):
345
- if self.debug_mode:
346
- print(f"[DEBUG] Match found with {strategy.__class__.__name__}")
347
- print(f"[DEBUG] Stopping strategy chain at line {pos + 1}")
348
- return StrategyResult(success=True, strategy_name=strategy.name, match_position=pos)
349
- return StrategyResult(success=False)
350
-
351
- def _find_matches(self, source_lines: List[str], pattern_lines: List[str],
352
- file_ext: Optional[str] = None) -> List[int]:
353
- """Find all matching positions using available strategies.
354
-
355
- Args:
356
- source_lines: List of source code lines
357
- pattern_lines: List of pattern lines to match
358
- file_ext: Optional file extension to determine strategies
359
-
360
- Returns:
361
- List[int]: List of matching line positions
362
- """
363
- strategies = self.get_strategies(file_ext)
364
-
365
- if self.debug_mode:
366
- print("\nTrying search strategies:")
367
- print("-" * 50)
368
-
369
- # Track positions already matched to avoid redundant attempts
370
- matched_positions = set()
371
- all_matches = []
372
-
373
- for strategy in strategies:
374
- strategy_name = strategy.__class__.__name__.replace('Strategy', '')
375
-
376
- if self.debug_mode:
377
- print(f"\n→ {strategy_name}...")
378
-
379
- for i in range(len(source_lines)):
380
- if i in matched_positions:
381
- continue
382
-
383
- if strategy.match(source_lines, pattern_lines, i, self):
384
- matched_positions.add(i)
385
- all_matches.append(i)
386
- if self.debug_mode:
387
- print(f"✓ Match found at line {i+1} using {strategy_name}")
388
-
389
- if all_matches and isinstance(strategy, ExactMatchStrategy):
390
- # If we found exact matches, no need to try other strategies
391
- break
392
-
393
- if self.debug_mode and all_matches:
394
- print(f"\nFound {len(all_matches)} total match(es) at line(s) {[m+1 for m in sorted(all_matches)]}")
395
-
396
- return sorted(all_matches)
397
-
398
- def _check_exact_match(self, source_lines: List[str], pattern_lines: List[str], pos: int) -> bool:
399
- """Check for exact line-by-line match at position.
400
-
401
- Args:
402
- source_lines: List of source code lines
403
- pattern_lines: List of pattern lines to match
404
- pos: Position to check for match
405
-
406
- Returns:
407
- bool: True if exact match found, False otherwise
408
- """
409
- if pos + len(pattern_lines) > len(source_lines):
410
- return False
411
- return all(source_lines[pos + j] == pattern_lines[j] for j in range(len(pattern_lines)))
@@ -1,10 +0,0 @@
1
- from dataclasses import dataclass
2
- from typing import Optional
3
-
4
- @dataclass
5
- class StrategyResult:
6
- """Encapsulates the result of a strategy match attempt."""
7
- success: bool
8
- strategy_name: Optional[str] = None
9
- match_position: Optional[int] = None
10
- file_type: Optional[str] = None
janito/shell/__init__.py DELETED
@@ -1,38 +0,0 @@
1
- """Shell package initialization for Janito."""
2
- from typing import Optional
3
- from prompt_toolkit import PromptSession
4
- from rich.console import Console
5
- from janito.config import config
6
- from janito.workspace.workset import Workset
7
- from .processor import CommandProcessor
8
- from .commands import register_commands
9
- from .registry import CommandRegistry
10
-
11
- def start_shell() -> None:
12
- """Start the Janito interactive shell."""
13
- # Create single registry instance
14
- registry = CommandRegistry()
15
- register_commands(registry)
16
-
17
- # Create shell components with shared registry
18
- from .prompt import create_shell_session
19
- session = create_shell_session(registry)
20
- processor = CommandProcessor(registry)
21
-
22
- # Initialize and show workset content
23
- workset = Workset()
24
- workset.refresh()
25
- workset.show()
26
-
27
- while True:
28
- try:
29
- text = session.prompt("janito🤖 ")
30
- if text.strip():
31
- processor.process_command(text)
32
- except KeyboardInterrupt:
33
- continue
34
- except EOFError:
35
- break
36
- except Exception as e:
37
- print(f"Error: {e}")
38
- print("Goodbye!")
janito/shell/bus.py DELETED
@@ -1,31 +0,0 @@
1
- """Command bus implementation for Janito shell."""
2
- from typing import Dict, Callable, Any, Optional
3
- from dataclasses import dataclass
4
-
5
- @dataclass
6
- class Command:
7
- """Command message for command bus."""
8
- name: str
9
- args: str
10
-
11
- class CommandBus:
12
- """Simple command bus implementation."""
13
- _instance = None
14
- _handlers: Dict[str, Callable[[Command], None]] = {}
15
-
16
- def __new__(cls):
17
- if cls._instance is None:
18
- cls._instance = super().__new__(cls)
19
- cls._instance._handlers = {}
20
- return cls._instance
21
-
22
- def register_handler(self, command_name: str, handler: Callable[[Command], None]) -> None:
23
- """Register a handler for a command."""
24
- self._handlers[command_name] = handler
25
-
26
- def handle(self, command: Command) -> None:
27
- """Handle a command by dispatching to appropriate handler."""
28
- if handler := self._handlers.get(command.name):
29
- handler(command)
30
- else:
31
- raise ValueError(f"No handler registered for command: {command.name}")
janito/shell/commands.py DELETED
@@ -1,136 +0,0 @@
1
- """Command system for Janito shell."""
2
- from rich.console import Console
3
- from rich.table import Table
4
- from prompt_toolkit import PromptSession
5
- from prompt_toolkit.shortcuts import clear as ptk_clear
6
- from prompt_toolkit.completion import PathCompleter
7
- from prompt_toolkit.document import Document
8
- from pathlib import Path
9
- from janito.config import config
10
- from janito.workspace import workset # Updated import
11
- from janito.workspace.analysis import analyze_workspace_content
12
- from .registry import CommandRegistry, Command, get_path_completer
13
-
14
- def handle_request(args: str) -> None:
15
- """Handle a change request."""
16
- if not args:
17
- Console().print("[red]Error: Change request required[/red]")
18
- return
19
- from janito.cli.commands import handle_request as cli_handle_request
20
- cli_handle_request(args)
21
-
22
- def handle_exit(_: str) -> None:
23
- """Handle exit command."""
24
- raise EOFError()
25
-
26
- def handle_clear(_: str) -> None:
27
- """Handle clear command."""
28
- ptk_clear()
29
-
30
- def handle_ask(args: str) -> None:
31
- """Handle ask command."""
32
- if not args:
33
- Console().print("[red]Error: Question required[/red]")
34
- return
35
- from janito.cli.commands import handle_ask as cli_handle_ask
36
- cli_handle_ask(args)
37
-
38
- def handle_help(args: str) -> None:
39
- """Handle help command."""
40
- console = Console()
41
- registry = CommandRegistry()
42
- command = args.strip() if args else None
43
- if command and (cmd := registry.get_command(command)):
44
- console.print(f"\n[bold]{command}[/bold]: {cmd.description}")
45
- if cmd.usage:
46
- console.print(f"Usage: {cmd.usage}")
47
- else:
48
- table = Table(title="Available Commands")
49
- table.add_column("Command", style="cyan")
50
- table.add_column("Description")
51
-
52
- for name, cmd in sorted(registry.get_commands().items()):
53
- table.add_row(name, cmd.description)
54
-
55
- console.print(table)
56
-
57
- def handle_include(args: str) -> None:
58
- """Handle include command."""
59
- console = Console()
60
- session = PromptSession()
61
-
62
- if not args:
63
- try:
64
- args = session.prompt("Enter paths (space separated): ")
65
- except (KeyboardInterrupt, EOFError):
66
- return
67
-
68
- paths = [p.strip() for p in args.split() if p.strip()]
69
- if not paths:
70
- console.print("[red]Error: At least one path required[/red]")
71
- return
72
-
73
- resolved_paths = []
74
- for path_str in paths:
75
- path = Path(path_str)
76
- if not path.is_absolute():
77
- path = config.workspace_dir / path
78
- resolved_paths.append(path.resolve())
79
-
80
- workset.include(resolved_paths)
81
- workset.show()
82
-
83
- console.print("[green]Updated include paths:[/green]")
84
- for path in resolved_paths:
85
- console.print(f" {path}")
86
-
87
- def handle_rinclude(args: str) -> None:
88
- """Handle recursive include command."""
89
- console = Console()
90
- session = PromptSession()
91
- completer = get_path_completer(only_directories=True)
92
-
93
- if not args:
94
- try:
95
- args = session.prompt("Enter directory paths (space separated): ", completer=completer)
96
- except (KeyboardInterrupt, EOFError):
97
- return
98
-
99
- paths = [p.strip() for p in args.split() if p.strip()]
100
- if not paths:
101
- console.print("[red]Error: At least one path required[/red]")
102
- return
103
-
104
- resolved_paths = []
105
- for path_str in paths:
106
- path = Path(path_str)
107
- if not path.is_absolute():
108
- path = config.workspace_dir / path
109
- resolved_paths.append(path.resolve())
110
-
111
- workset.recursive(resolved_paths)
112
- workset.include(resolved_paths) # Add recursive paths to include paths
113
- workset.refresh()
114
- workset.show()
115
-
116
- console.print("[green]Updated recursive include paths:[/green]")
117
- for path in resolved_paths:
118
- console.print(f" {path}")
119
-
120
- def register_commands(registry: CommandRegistry) -> None:
121
- """Register all available commands."""
122
- # Register main commands
123
- registry.register(Command("/clear", "Clear the terminal screen", None, handle_clear))
124
- registry.register(Command("/request", "Submit a change request", "/request <change request text>", handle_request))
125
- registry.register(Command("/ask", "Ask a question about the codebase", "/ask <question>", handle_ask))
126
- registry.register(Command("/quit", "Exit the shell", None, handle_exit))
127
- registry.register(Command("/help", "Show help for commands", "/help [command]", handle_help))
128
- registry.register(Command("/include", "Set paths to include in analysis", "/include <path1> [path2 ...]", handle_include, get_path_completer()))
129
- registry.register(Command("/rinclude", "Set paths to include recursively", "/rinclude <path1> [path2 ...]", handle_rinclude, get_path_completer(True)))
130
-
131
- # Register aliases
132
- registry.register_alias("clear", "/clear")
133
- registry.register_alias("quit", "/quit")
134
- registry.register_alias("help", "/help")
135
- registry.register_alias("/inc", "/include")
136
- registry.register_alias("/rinc", "/rinclude")
janito/shell/history.py DELETED
@@ -1,20 +0,0 @@
1
- """Command history management for Janito shell."""
2
- from pathlib import Path
3
- from typing import List, Optional
4
- from prompt_toolkit.history import FileHistory
5
-
6
- class CommandHistory:
7
- """Manages shell command history."""
8
-
9
- def __init__(self, history_file: Optional[Path] = None):
10
- if history_file is None:
11
- history_file = Path.home() / ".janito_history"
12
- self.history = FileHistory(str(history_file))
13
-
14
- def add(self, command: str) -> None:
15
- """Add a command to history."""
16
- self.history.append_string(command)
17
-
18
- def get_last(self, n: int = 10) -> List[str]:
19
- """Get last n commands from history."""
20
- return list(self.history.get_strings())[-n:]
janito/shell/processor.py DELETED
@@ -1,32 +0,0 @@
1
- """Command processor for Janito shell."""
2
- from typing import Optional
3
- from rich.console import Console
4
- from .registry import CommandRegistry
5
-
6
- class CommandProcessor:
7
- """Processes shell commands."""
8
-
9
- def __init__(self, registry: CommandRegistry) -> None:
10
- """Initialize command processor with registry."""
11
- super().__init__()
12
- self.console = Console()
13
- self.registry = registry
14
-
15
- def process_command(self, command_line: str) -> None:
16
- """Process a command line input."""
17
- command_line = command_line.strip()
18
- if not command_line:
19
- return
20
-
21
- parts = command_line.split(maxsplit=1)
22
- cmd = parts[0].lower()
23
- args = parts[1] if len(parts) > 1 else ""
24
-
25
- if command := self.registry.get_command(cmd):
26
- command.handler(args)
27
- else:
28
- # Treat as request command
29
- if request_cmd := self.registry.get_command("/request"):
30
- request_cmd.handler(command_line)
31
- else:
32
- self.console.print("[red]Error: Request command not registered[/red]")