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,192 +0,0 @@
1
- # Search/Replace Module
2
-
3
- A smart search and replace module that handles code indentation and provides debugging capabilities for failed searches.
4
-
5
- ## Usage
6
-
7
- ### As a Module
8
-
9
- ```python
10
- from janito.search_replace import SearchReplacer
11
-
12
- # Basic search/replace
13
- source_code = """
14
- def hello():
15
- print("Hello")
16
- print("World")
17
- """
18
-
19
- search = """ print("Hello")
20
- print("World")"""
21
-
22
- replacement = """ print("Hi")
23
- print("Universe")"""
24
-
25
- replacer = SearchReplacer(source_code, search, replacement)
26
- modified = replacer.replace()
27
- ```
28
-
29
- ### Command Line Debugging
30
-
31
- When a search fails, a debug file is automatically created in `.janito/change_history/`. You can debug these files using:
32
-
33
- ```bash
34
- python -m janito.search_replace <debug_file>
35
- ```
36
-
37
- Example debug file format:
38
- ```
39
- Test: Failed search in example.py
40
- ========================================
41
- Original:
42
- def hello():
43
- print("Hello")
44
- print("World")
45
- ========================================
46
- Search pattern:
47
- print("Hi")
48
- print("World")
49
- ========================================
50
- ```
51
-
52
- ## Features
53
-
54
- - Indentation-aware searching
55
- - Multiple search strategies:
56
- - ExactMatch: Matches content with exact indentation
57
- - ExactContent: Matches content ignoring indentation
58
- - IndentAware: Matches preserving relative indentation
59
- - Debug mode with detailed indentation analysis
60
- - File extension specific behavior
61
- - Automatic debug file generation for failed searches
62
-
63
- ## Search Strategies
64
-
65
- The module employs multiple search strategies in a fallback chain to find the best match. Each strategy has specific behaviors and use cases:
66
-
67
- ### ExactMatch Strategy
68
- - Matches content exactly, including all whitespace and indentation
69
- - Strictest matching strategy
70
- - Best for precise replacements where indentation matters
71
- - Example:
72
- ```python
73
- # Pattern:
74
- def hello():
75
- print("Hi")
76
-
77
- # Will only match exact indentation:
78
- def hello():
79
- print("Hi")
80
-
81
- # Won't match different indentation:
82
- def hello():
83
- print("Hi")
84
- ```
85
-
86
- ### IndentAware Strategy
87
- - Preserves relative indentation between lines
88
- - Allows different base indentation levels
89
- - Ideal for matching code blocks inside functions/classes
90
- - Example:
91
- ```python
92
- # Pattern:
93
- print("Hello")
94
- print("World")
95
-
96
- # Matches with different base indentation:
97
- def test():
98
- print("Hello")
99
- print("World")
100
-
101
- def other():
102
- print("Hello")
103
- print("World")
104
-
105
- # Won't match if relative indentation differs:
106
- def wrong():
107
- print("Hello")
108
- print("World")
109
- ```
110
-
111
- ### ExactContent Strategy
112
- - Ignores all indentation
113
- - Matches content after stripping whitespace
114
- - Useful for matching code regardless of formatting
115
- - Example:
116
- ```python
117
- # Pattern:
118
- print("Hello")
119
- print("World")
120
-
121
- # Matches any indentation:
122
- print("Hello")
123
- print("World")
124
-
125
- # Also matches:
126
- print("Hello")
127
- print("World")
128
- ```
129
-
130
- ### ExactContentNoComments Strategy
131
- - Ignores indentation, comments, and empty lines
132
- - Most flexible strategy
133
- - Perfect for matching code with varying comments/formatting
134
- - Example:
135
- ```python
136
- # Pattern:
137
- print("Hello") # greeting
138
-
139
- print("World") # message
140
-
141
- # Matches all these variations:
142
- def test():
143
- print("Hello") # different comment
144
- # some comment
145
- print("World")
146
-
147
- # Or:
148
- print("Hello") # no comment
149
- print("World") # different note
150
- ```
151
-
152
- ### ExactContentNoCommentsFirstLinePartial Strategy
153
- - Matches first line partially, ignoring comments
154
- - Useful for finding code fragments or partial matches
155
- - Example:
156
- ```python
157
- # Pattern:
158
- print("Hello")
159
-
160
- # Matches partial content:
161
- message = print("Hello") + "extra"
162
- result = print("Hello, World")
163
- ```
164
-
165
- ### Strategy Selection and File Types
166
-
167
- Strategies are tried in the following order:
168
- 1. ExactMatch
169
- 2. IndentAware
170
- 3. ExactContent
171
- 4. ExactContentNoComments
172
- 5. ExactContentNoCommentsFirstLinePartial
173
-
174
- File extension specific behavior:
175
-
176
- | File Type | Available Strategies |
177
- |-----------|---------------------|
178
- | Python (.py) | All strategies |
179
- | Java (.java) | All strategies |
180
- | JavaScript (.js) | All strategies |
181
- | TypeScript (.ts) | All strategies |
182
- | Other files | ExactMatch, ExactContent, ExactContentNoComments, ExactContentNoCommentsFirstLinePartial |
183
-
184
- The module automatically selects the appropriate strategies based on the file type and tries them in order until a match is found.
185
-
186
- ## Debug Output
187
-
188
- When debugging failed searches, the module provides:
189
- - Visual whitespace markers (· for spaces, → for tabs)
190
- - Indentation analysis
191
- - Line-by-line matching attempts
192
- - Strategy selection information
@@ -1,7 +0,0 @@
1
- from .core import SearchReplacer, PatternNotFoundException
2
- from .searcher import Searcher
3
- from .replacer import Replacer
4
- from .parser import parse_test_file
5
- from .strategy_result import StrategyResult
6
-
7
- __all__ = ['SearchReplacer', 'PatternNotFoundException', 'Searcher', 'Replacer', 'parse_test_file', 'StrategyResult']
@@ -1,21 +0,0 @@
1
- """Main entry point for search/replace module."""
2
-
3
- from pathlib import Path
4
- import sys
5
- import argparse
6
- from .play import play_file
7
-
8
- def main():
9
- parser = argparse.ArgumentParser(description="Debug search/replace patterns")
10
- parser.add_argument('file', type=Path, help='Test file to analyze')
11
-
12
- args = parser.parse_args()
13
-
14
- if not args.file.exists():
15
- print(f"Error: Test file not found: {args.file}")
16
- sys.exit(1)
17
-
18
- play_file(args.file)
19
-
20
- if __name__ == "__main__":
21
- main()
@@ -1,120 +0,0 @@
1
- from typing import Optional, List
2
- from pathlib import Path
3
- import os
4
- from datetime import datetime
5
- from .logger import log_match, log_failure
6
- from .searcher import Searcher
7
- from .replacer import Replacer
8
-
9
- class PatternNotFoundException(Exception):
10
- """Raised when the search pattern is not found in the source code."""
11
- pass
12
-
13
- class SearchReplacer:
14
- """Handles indentation-aware search and replace operations on Python source code."""
15
-
16
- def __init__(self, source_code: str, search_pattern: str, replacement: Optional[str] = None,
17
- file_ext: Optional[str] = None, debug: bool = False):
18
- """Initialize with source code and patterns."""
19
- self.source_code = source_code.rstrip()
20
- self.search_pattern = search_pattern.rstrip()
21
- self.replacement = replacement.rstrip() if replacement else None
22
- self.file_ext = file_ext.lower() if file_ext else None
23
- self.pattern_found = False
24
- self.searcher = Searcher(debug=debug)
25
- self.replacer = Replacer(debug=debug)
26
-
27
- # Initialize pattern base indent
28
- first_line, _ = self.searcher.get_first_non_empty_line(self.search_pattern)
29
- self.pattern_base_indent = len(self.searcher.get_indentation(first_line)) if first_line else 0
30
-
31
- def find_pattern(self) -> bool:
32
- """Search for pattern with indentation awareness."""
33
- try:
34
- source_lines = self.source_code.splitlines()
35
- search_first, _ = self.searcher.get_first_non_empty_line(self.search_pattern)
36
- search_indent = self.searcher.get_indentation(search_first)
37
- normalized_pattern = self.searcher.normalize_pattern(self.search_pattern, search_indent)
38
- matches = self._find_matches(source_lines, normalized_pattern)
39
- return bool(self.searcher._find_best_match_position(matches, source_lines, self.pattern_base_indent))
40
- except Exception:
41
- return False
42
-
43
- def replace(self) -> str:
44
- """Perform the search and replace operation."""
45
- if self.replacement is None:
46
- if not self.find_pattern():
47
- raise PatternNotFoundException("Pattern not found")
48
- return self.source_code
49
-
50
- source_lines = self.source_code.splitlines()
51
- search_first, _ = self.searcher.get_first_non_empty_line(self.search_pattern)
52
- search_indent = self.searcher.get_indentation(search_first)
53
- normalized_pattern = self.searcher.normalize_pattern(self.search_pattern, search_indent)
54
-
55
- matches = self._find_matches(source_lines, normalized_pattern)
56
- best_pos = self.searcher._find_best_match_position(matches, source_lines, self.pattern_base_indent)
57
-
58
- if best_pos is None:
59
- # Log failed match if not in debug mode
60
- if not self.searcher.debug_mode:
61
- log_failure(self.file_ext)
62
- raise PatternNotFoundException("Pattern not found")
63
-
64
- if self.searcher.debug_mode:
65
- pattern_lines = len(normalized_pattern.splitlines())
66
- replacement_lines = len(self.replacement.splitlines()) if self.replacement else 0
67
- print(f"\n[DEBUG] Replacing {pattern_lines} lines with {replacement_lines} lines")
68
- context_start = max(0, best_pos - 2)
69
- context_end = min(len(source_lines), best_pos + len(normalized_pattern.splitlines()) + 2)
70
- print("\n[DEBUG] Context before replacement:")
71
- for i in range(context_start, context_end):
72
- prefix = ">>> " if context_start <= i < best_pos + len(normalized_pattern.splitlines()) else " "
73
- print(f"[DEBUG] {prefix}Line {i + 1}: {source_lines[i]}")
74
-
75
- result = self._apply_replacement(source_lines, best_pos, normalized_pattern)
76
-
77
- if self.searcher.debug_mode:
78
- print("\n[DEBUG] Context after replacement:")
79
- result_lines = result.splitlines()
80
- for i in range(context_start, context_end):
81
- prefix = ">>> " if context_start <= i < best_pos + len(self.replacement.splitlines()) else " "
82
- print(f"[DEBUG] {prefix}Line {i + 1}: {result_lines[i]}")
83
-
84
- return result
85
-
86
- def _find_matches(self, source_lines, normalized_pattern):
87
- """Find all possible matches in source."""
88
- pattern_lines = normalized_pattern.splitlines()
89
- return self.searcher._find_matches(source_lines, pattern_lines, self.file_ext)
90
-
91
- def _apply_replacement(self, source_lines, match_pos, normalized_pattern):
92
- """Apply replacement at the matched position."""
93
- result_lines = []
94
- i = 0
95
- while i < len(source_lines):
96
- if i == match_pos:
97
- self.pattern_found = True
98
- # Log successful match if not in debug mode
99
- # get the
100
- if not self.searcher.debug_mode:
101
- log_match("Strategy", self.file_ext)
102
- match_indent = self.searcher.get_indentation(source_lines[i])
103
- replacement_lines = self.replacer.create_indented_replacement(
104
- match_indent, self.search_pattern, self.replacement
105
- )
106
- result_lines.extend(replacement_lines)
107
- i += len(normalized_pattern.splitlines())
108
- else:
109
- result_lines.append(source_lines[i])
110
- i += 1
111
- return '\n'.join(result_lines)
112
-
113
- def _try_match_at_position(self, pos, source_lines, normalized_pattern):
114
- """Check if pattern matches at given position."""
115
- pattern_lines = normalized_pattern.splitlines()
116
- strategies = self.searcher.get_strategies(self.file_ext)
117
- result = self.searcher.try_match_with_strategies(source_lines, pattern_lines, pos, strategies)
118
- if result.success and not self.searcher.debug_mode:
119
- log_match(result.strategy_name, self.file_ext)
120
- return result.success
@@ -1,35 +0,0 @@
1
- import logging
2
- from datetime import datetime
3
- from pathlib import Path
4
- from typing import Optional
5
-
6
- # Configure logging
7
- logger = logging.getLogger("janito.search_replace")
8
- logger.setLevel(logging.INFO)
9
-
10
- # Create formatter
11
- formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
12
-
13
- # Create file handler
14
- def setup_file_handler():
15
- """Setup file handler for logging if .janito directory exists"""
16
- if Path(".janito").exists():
17
- fh = logging.FileHandler(".janito/search_logs.txt")
18
- fh.setFormatter(formatter)
19
- logger.addHandler(fh)
20
-
21
- setup_file_handler()
22
-
23
- def log_match(strategy_name: str, file_type: Optional[str] = None):
24
- """Log successful match with strategy info"""
25
- msg = f"Match found using {strategy_name}"
26
- if file_type:
27
- msg += f" for file type {file_type}"
28
- logger.info(msg)
29
-
30
- def log_failure(file_type: Optional[str] = None):
31
- """Log failed match attempt"""
32
- msg = "Failed to match pattern"
33
- if file_type:
34
- msg += f" for file type {file_type}"
35
- logger.warning(msg)
@@ -1,52 +0,0 @@
1
- from pathlib import Path
2
- from typing import List, Dict
3
-
4
- def parse_test_file(filepath: Path) -> List[Dict]:
5
- """Parse a test file containing test cases. Replacement section is optional."""
6
- test_cases = []
7
- current_test = {}
8
- current_section = None
9
- current_content = []
10
-
11
- try:
12
- content = filepath.read_text()
13
- lines = content.splitlines()
14
-
15
- for line in lines:
16
- if line.startswith("Test: "):
17
- if current_test:
18
- if current_section and current_content:
19
- current_test[current_section] = "\n".join(current_content)
20
- test_cases.append(current_test)
21
- current_test = {"name": line[6:].strip(), "expect_success": True}
22
- current_section = None
23
- current_content = []
24
- elif line.startswith("Original:"):
25
- if current_section and current_content:
26
- current_test[current_section] = "\n".join(current_content)
27
- current_section = "source"
28
- current_content = []
29
- elif line.startswith("Search pattern:"):
30
- if current_section and current_content:
31
- current_test[current_section] = "\n".join(current_content)
32
- current_section = "search"
33
- current_content = []
34
- elif line.startswith("Replacement:"):
35
- if current_section and current_content:
36
- current_test[current_section] = "\n".join(current_content)
37
- current_section = "replacement"
38
- current_content = []
39
- elif not line.startswith("="): # Skip separator lines
40
- if current_section:
41
- current_content.append(line)
42
-
43
- # Add last test case
44
- if current_test:
45
- if current_section and current_content:
46
- current_test[current_section] = "\n".join(current_content)
47
- test_cases.append(current_test)
48
-
49
- return test_cases
50
- except Exception as e:
51
- print(f"Error parsing test file: {e}")
52
- return []
@@ -1,61 +0,0 @@
1
- from pathlib import Path
2
- from typing import Optional
3
- from .parser import parse_test_file
4
- from .core import SearchReplacer
5
- import re
6
-
7
- def _extract_file_ext(test_info: str) -> Optional[str]:
8
- """Extract file extension from test description."""
9
- # Try to find filename or extension in the test info
10
- ext_match = re.search(r'\.([a-zA-Z0-9]+)\b', test_info)
11
- if (ext_match):
12
- return f".{ext_match.group(1).lower()}"
13
-
14
- # Look for language mentions
15
- lang_map = {
16
- 'python': '.py',
17
- 'javascript': '.js',
18
- 'typescript': '.ts',
19
- 'java': '.java'
20
- }
21
-
22
- for lang, ext in lang_map.items():
23
- if lang.lower() in test_info.lower():
24
- return ext
25
-
26
- return None
27
-
28
- def play_file(filepath: Path):
29
- """Play back a test file and show detailed debugging info."""
30
- test_cases = parse_test_file(filepath)
31
-
32
- for test in test_cases:
33
- print(f"\nTest: {test['name']}")
34
- print("=" * 50)
35
-
36
- if 'source' not in test or 'search' not in test:
37
- print("Invalid test case - missing source or search pattern")
38
- continue
39
-
40
- file_ext = _extract_file_ext(test['name'])
41
- print(f"\nFile type: {file_ext or 'unknown'}")
42
-
43
- replacer = SearchReplacer(
44
- source_code=test['source'],
45
- search_pattern=test['search'],
46
- replacement=test.get('replacement'),
47
- file_ext=file_ext,
48
- debug=True
49
- )
50
-
51
- try:
52
- print("\nAttempting search/replace...")
53
- result = replacer.replace()
54
- print("\nResult:")
55
- print("-" * 50)
56
- print(result)
57
-
58
- except Exception as e:
59
- print(f"\nError: {str(e)}")
60
-
61
- print("\n" + "="*50)
@@ -1,36 +0,0 @@
1
- from typing import List
2
- from .searcher import Searcher
3
-
4
- class Replacer:
5
- """Handles replacement operations with proper indentation."""
6
-
7
- def __init__(self, debug: bool = False):
8
- """Initialize replacer with debug mode."""
9
- self.searcher = Searcher(debug=debug)
10
- self.debug_mode = debug
11
-
12
- def create_indented_replacement(self, match_indent: str, search_pattern: str, replacement: str) -> List[str]:
13
- """Create properly indented replacement lines."""
14
- search_first, search_start_idx = self.searcher.get_first_non_empty_line(search_pattern)
15
- replace_first, replace_start_idx = self.searcher.get_first_non_empty_line(replacement)
16
-
17
- search_indent = self.searcher.get_indentation(search_first)
18
- replace_indent = self.searcher.get_indentation(replace_first)
19
-
20
- replace_lines = replacement.splitlines()
21
- indented_replacement = []
22
-
23
- # Calculate indentation shifts
24
- context_shift = len(match_indent) - len(search_indent)
25
- pattern_shift = len(replace_indent) - len(search_indent)
26
-
27
- for i, line in enumerate(replace_lines):
28
- if i < replace_start_idx or not line.strip():
29
- indented_replacement.append('')
30
- else:
31
- line_indent = self.searcher.get_indentation(line)
32
- rel_indent = len(line_indent) - len(replace_indent)
33
- final_indent = ' ' * (len(match_indent) + rel_indent)
34
- indented_replacement.append(final_indent + line.lstrip())
35
-
36
- return indented_replacement