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,58 +0,0 @@
1
- from pathlib import Path
2
- from typing import Set, List
3
- import shutil
4
- from rich.console import Console
5
- from janito.config import config
6
- from ..parser import FileChange, ChangeOperation
7
-
8
- def verify_changes(changes: List[FileChange]) -> tuple[bool, str]:
9
- """Verify changes can be safely applied to workspace_dir.
10
- Returns (is_safe, error_message)."""
11
- for change in changes:
12
- source_path = config.workspace_dir / change.name
13
-
14
- if change.operation == ChangeOperation.CREATE_FILE:
15
- if source_path.exists():
16
- return False, f"Cannot create {change.name} - already exists"
17
-
18
- elif change.operation in (ChangeOperation.MOVE_FILE, ChangeOperation.RENAME_FILE):
19
- if not source_path.exists():
20
- return False, f"Cannot {change.operation.name.lower()} non-existent file {change.name}"
21
- target_path = config.workspace_dir / change.target
22
- if target_path.exists():
23
- return False, f"Cannot {change.operation.name.lower()} {change.name} to {change.target} - target already exists"
24
-
25
-
26
- return True, ""
27
-
28
- def apply_changes(changes: List[FileChange], preview_dir: Path, console: Console) -> bool:
29
- """Apply all changes from preview to workspace_dir.
30
- Returns success status."""
31
- is_safe, error = verify_changes(changes)
32
- if not is_safe:
33
- console.print(f"[red]Error: {error}[/red]")
34
- return False
35
-
36
- console.print("\n[blue]Applying changes to working directory...[/blue]")
37
-
38
- for change in changes:
39
- if change.operation == ChangeOperation.REMOVE_FILE:
40
- remove_from_workspace_dir(change.name, console)
41
- else:
42
- filepath = change.target if change.operation == ChangeOperation.RENAME_FILE else change.name
43
- target_path = config.workspace_dir / filepath
44
- preview_path = preview_dir / filepath
45
-
46
- target_path.parent.mkdir(parents=True, exist_ok=True)
47
- if preview_path.exists():
48
- shutil.copy2(preview_path, target_path)
49
- console.print(f"[dim]Applied changes to {filepath}[/dim]")
50
-
51
- return True
52
-
53
- def remove_from_workspace_dir(filepath: Path, console: Console) -> None:
54
- """Remove file from working directory"""
55
- target_path = config.workspace_dir / filepath
56
- if target_path.exists():
57
- target_path.unlink()
58
- console.print(f"[red]Removed {filepath}[/red]")
janito/change/core.py DELETED
@@ -1,124 +0,0 @@
1
- from pathlib import Path
2
- from typing import Optional, Tuple, List
3
- from rich.console import Console
4
- from rich.prompt import Confirm
5
- from rich.panel import Panel
6
- from rich.columns import Columns
7
- from rich import box
8
-
9
- from janito.common import progress_send_message
10
- from janito.change.history import save_changes_to_history
11
- from janito.config import config
12
- from janito.workspace.workset import Workset # Update import to use Workset directly
13
- from .viewer import preview_all_changes
14
- from janito.workspace.analysis import analyze_workspace_content as show_content_stats
15
-
16
- from . import (
17
- build_change_request_prompt,
18
- parse_response,
19
- setup_workspace_dir_preview,
20
- ChangeApplier
21
- )
22
-
23
- from .analysis import analyze_request
24
-
25
- def process_change_request(
26
- request: str,
27
- preview_only: bool = False,
28
- debug: bool = False
29
- ) -> Tuple[bool, Optional[Path]]:
30
- """
31
- Process a change request through the main flow.
32
- Return:
33
- success: True if the request was processed successfully
34
- history_file: Path to the saved history file
35
- """
36
- console = Console()
37
- workset = Workset() # Create workset instance
38
-
39
-
40
- # Analyze workspace content
41
- workset.show()
42
-
43
- # Get analysis of the request using workset content
44
- analysis = analyze_request(request)
45
- if not analysis:
46
- console.print("[red]Analysis failed or interrupted[/red]")
47
- return False, None
48
-
49
- # Build and send prompt
50
- prompt = build_change_request_prompt(request, analysis.format_option_text())
51
- response = progress_send_message(prompt)
52
- if not response:
53
- console.print("[red]Failed to get response from AI[/red]")
54
- return False, None
55
-
56
- # Save to history and process response
57
- history_file = save_changes_to_history(response, request)
58
-
59
- # Parse changes
60
- changes = parse_response(response)
61
- if not changes:
62
- console.print("[yellow]No changes found in response[/yellow]")
63
- return False, None
64
-
65
- # Show request and response info
66
- response_info = extract_response_info(response)
67
- console.print("\n")
68
- console.print(Columns([
69
- Panel(request, title="User Request", border_style="cyan", box=box.ROUNDED),
70
- Panel(
71
- response_info if response_info else "No additional information provided",
72
- title="Response Information",
73
- border_style="green",
74
- box=box.ROUNDED
75
- )
76
- ], equal=True, expand=True))
77
- console.print("\n")
78
-
79
- if preview_only:
80
- preview_all_changes(console, changes)
81
- return True, history_file
82
-
83
- # Apply changes
84
- _, preview_dir = setup_workspace_dir_preview()
85
- applier = ChangeApplier(preview_dir, debug=debug)
86
-
87
- success, _ = applier.apply_changes(changes)
88
- if success:
89
- preview_all_changes(console, changes)
90
-
91
- if not config.auto_apply:
92
- apply_changes = Confirm.ask("[cyan]Apply changes to working dir?[/cyan]")
93
- else:
94
- apply_changes = True
95
- console.print("[cyan]Auto-applying changes to working dir...[/cyan]")
96
-
97
- if apply_changes:
98
- applier.apply_to_workspace_dir(changes)
99
- console.print("[green]Changes applied successfully[/green]")
100
- else:
101
- console.print("[yellow]Changes were not applied[/yellow]")
102
-
103
- return success, history_file
104
-
105
-
106
- def extract_response_info(response: str) -> str:
107
- """Extract information after END_OF_INSTRUCTIONS marker"""
108
- if not response:
109
- return ""
110
-
111
- # Find the marker
112
- marker = "END_INSTRUCTIONS"
113
- marker_pos = response.find(marker)
114
-
115
- if marker_pos == -1:
116
- return ""
117
-
118
- # Get text after marker, skipping the marker itself
119
- info = response[marker_pos + len(marker):].strip()
120
-
121
- # Remove any XML-style tags
122
- info = info.replace("<Extra info about what was implemented/changed goes here>", "")
123
-
124
- return info.strip()
janito/change/history.py DELETED
@@ -1,44 +0,0 @@
1
- from pathlib import Path
2
- from datetime import datetime
3
- from typing import Optional, Tuple
4
- from janito.config import config
5
-
6
- # Set fixed timestamp when module is loaded
7
- APP_TIMESTAMP = datetime.now().strftime("%Y%m%d_%H%M%S")
8
-
9
- def get_history_path() -> Path:
10
- """Create and return the history directory path"""
11
- history_dir = config.workspace_dir / '.janito' / 'change_history'
12
- history_dir.mkdir(parents=True, exist_ok=True)
13
- return history_dir
14
-
15
- def determine_history_file_type(filepath: Path) -> str:
16
- """Determine the type of saved file based on its name"""
17
- name = filepath.name.lower()
18
- if '_changes_failed' in name:
19
- return 'changes_failed'
20
- elif 'changes' in name:
21
- return 'changes'
22
- elif 'selected' in name:
23
- return 'selected'
24
- elif 'analysis' in name:
25
- return 'analysis'
26
- elif 'response' in name:
27
- return 'response'
28
- return 'unknown'
29
-
30
- def save_changes_to_history(content: str, request: str) -> Path:
31
- """Save change content to history folder with timestamp and request info"""
32
- history_dir = get_history_path()
33
-
34
- # Create history entry with request and changes
35
- filename = f"{APP_TIMESTAMP}_changes.txt"
36
- history_file = history_dir / filename
37
- history_content = f"""Request: {request}
38
- Timestamp: {APP_TIMESTAMP}
39
-
40
- Changes:
41
- {content}
42
- """
43
- history_file.write_text(history_content)
44
- return history_file
@@ -1,7 +0,0 @@
1
- from enum import Enum, auto
2
-
3
- class TextOperation(Enum):
4
- """Supported text modification operations"""
5
- REPLACE = auto()
6
- APPEND = auto()
7
- DELETE = auto()
janito/change/parser.py DELETED
@@ -1,287 +0,0 @@
1
- import uuid
2
- from dataclasses import dataclass, field
3
- from pathlib import Path
4
- from typing import List, Optional
5
- from rich.console import Console
6
- from janito.config import config
7
- from janito.clear_statement_parser.parser import Statement, StatementParser
8
-
9
- console = Console(stderr=True)
10
-
11
- from .prompts import CHANGE_REQUEST_PROMPT
12
-
13
-
14
- import uuid
15
- from dataclasses import dataclass, field
16
- from pathlib import Path
17
- from typing import List, Optional, Dict, Union
18
- from rich.console import Console
19
- from enum import Enum, auto
20
-
21
- class ChangeOperation(Enum):
22
- CREATE_FILE = auto()
23
- REPLACE_FILE = auto()
24
- RENAME_FILE = auto()
25
- REMOVE_FILE = auto()
26
- MODIFY_FILE = auto()
27
- MOVE_FILE = auto()
28
-
29
- @dataclass
30
- class TextChange:
31
- """Represents a search and replace/delete operation"""
32
- search_content: Optional[str] = None
33
- replace_content: Optional[str] = None
34
- reason: Optional[str] = None
35
- operation: Optional[str] = None
36
-
37
- @property
38
- def is_append(self) -> bool:
39
- return self.operation == 'Append'
40
-
41
- @property
42
- def is_delete(self) -> bool:
43
- return self.operation == 'Delete' or (self.search_content and not self.replace_content)
44
-
45
- def validate(self) -> bool:
46
- """Validate the text change operation"""
47
- if not self.search_content and self.replace_content is None:
48
- return False
49
- return True
50
-
51
- @dataclass
52
- class FileChange:
53
- """Represents a file change operation"""
54
- operation: ChangeOperation
55
- name: Path # Changed back from path to name
56
- target: Optional[Path] = None
57
- source: Optional[Path] = None
58
- content: Optional[str] = None
59
- text_changes: List[TextChange] = field(default_factory=list)
60
- original_content: Optional[str] = None
61
- reason: Optional[str] = None
62
-
63
- def add_text_changes(self, changes: List[TextChange]):
64
- """Add multiple text changes to the existing list"""
65
- self.text_changes.extend(changes)
66
-
67
- @classmethod
68
- def from_dict(cls, data: Dict[str, Union[str, Path]]) -> 'FileChange':
69
- """Create FileChange instance from dictionary data"""
70
- operation = ChangeOperation[data['operation'].upper()]
71
- return cls(
72
- operation=operation,
73
- name=Path(data['name']), # Changed back to name
74
- target=Path(data['target']) if data.get('target') else None,
75
- source=Path(data.get('source')) if data.get('source') else None,
76
- content=data.get('content'),
77
- reason=data.get('reason')
78
- )
79
-
80
- def validate_required_parameters(self) -> bool:
81
- """Validate the file change operation and raise detailed errors if parameters are missing"""
82
- if self.operation == ChangeOperation.RENAME_FILE:
83
- if not self.source:
84
- raise ValueError(f"Missing 'source' parameter for {self.operation.name}")
85
- if not self.target:
86
- raise ValueError(f"Missing 'target' parameter for {self.operation.name}")
87
-
88
- elif self.operation in (ChangeOperation.CREATE_FILE, ChangeOperation.REPLACE_FILE):
89
- if not self.content:
90
- raise ValueError(f"Missing 'content' parameter for {self.operation.name}")
91
-
92
- elif self.operation == ChangeOperation.MODIFY_FILE:
93
- if not self.text_changes:
94
- raise ValueError(f"No closed text changes found for {self.operation.name}")
95
-
96
- return True
97
-
98
- class CommandParser:
99
- def __init__(self, debug: bool = False):
100
- self.debug = debug
101
- self.console = Console(stderr=True)
102
-
103
- def parse_statements(self, statements: List[Statement]) -> List[FileChange]:
104
- """Parse a list of Statement objects into FileChange objects"""
105
- if self.debug:
106
- self.console.print("[dim]Starting to parse statements...[/dim]")
107
-
108
- changes = []
109
-
110
- for statement in statements:
111
- statement_key = statement.name.upper().replace(' ', '_')
112
- supported_opers = [op.name.title().upper() for op in ChangeOperation]
113
- if statement_key not in supported_opers:
114
- raise Exception(f"{statement_key} not in supported statements: {supported_opers}")
115
-
116
- change = self.convert_statement_to_filechange(statement)
117
- if not change:
118
- raise Exception(f"Invalid change found: {statement.name}")
119
- if not change.validate_required_parameters():
120
- raise Exception(f"Missing required parameters for change: {statement.name}")
121
- changes.append(change)
122
- return changes
123
-
124
- def convert_statement_to_filechange(self, statement: Statement) -> Optional[FileChange]:
125
- """Convert a Statement to a FileChange object"""
126
- try:
127
- operation = ChangeOperation[statement.name.upper().replace(' ', '_')]
128
- change = FileChange(
129
- operation=operation,
130
- name=Path(statement.parameters.get('name', '')), # Changed back to name
131
- reason=statement.parameters.get('reason')
132
- )
133
-
134
- if 'target' in statement.parameters:
135
- change.target = Path(statement.parameters['target'])
136
- if 'source' in statement.parameters:
137
- change.source = Path(statement.parameters['source'])
138
- change.name = Path(statement.parameters['source']) # Changed back to name
139
-
140
- content = statement.parameters.get('content')
141
- if content:
142
- change.content = self._clean_content(content)
143
-
144
- # Handle multiple Changes blocks - combine all text changes
145
- all_text_changes = []
146
- for block_name, block_statements in statement.blocks:
147
- # Handle both numbered (Changes#1) and unnumbered (Changes) blocks
148
- base_name = block_name.split('#')[0]
149
- if base_name == 'Changes':
150
- if self.debug:
151
- self.console.print(f"[dim]Processing Changes block: {block_name}[/dim]")
152
- new_changes = self.parse_modifications_from_list(block_statements)
153
- all_text_changes.extend(new_changes)
154
-
155
- if all_text_changes:
156
- change.text_changes = all_text_changes
157
-
158
- return change
159
- except Exception as e:
160
- if self.debug:
161
- self.console.print(f"[red]Error converting statement: {e}[/red]")
162
- return None
163
-
164
- def parse_modifications_from_list(self, mod_statements: List[Statement]) -> List[TextChange]:
165
- """Convert parsed modifications list to TextChange objects"""
166
- modifications = []
167
-
168
- for statement in mod_statements:
169
- try:
170
- if statement.name == 'Replace':
171
- mod = TextChange(
172
- search_content=self._clean_content(statement.parameters.get('search', '')),
173
- replace_content=self._clean_content(statement.parameters.get('with', '')),
174
- reason=statement.parameters.get('reason'),
175
- operation='Replace'
176
- )
177
- elif statement.name == 'Delete':
178
- mod = TextChange(
179
- search_content=self._clean_content(statement.parameters.get('search', '')),
180
- reason=statement.parameters.get('reason'),
181
- operation='Delete'
182
- )
183
- elif statement.name == 'Append':
184
- mod = TextChange(
185
- search_content='',
186
- replace_content=self._clean_content(statement.parameters.get('content', '')),
187
- reason=statement.parameters.get('reason'),
188
- operation='Append'
189
- )
190
- else:
191
- continue
192
-
193
- if mod.validate():
194
- modifications.append(mod)
195
- except Exception as e:
196
- if self.debug:
197
- self.console.print(f"[red]Error processing modification: {e}[/red]")
198
- continue
199
-
200
- return modifications
201
-
202
- @staticmethod
203
- def _clean_content(content: str) -> str:
204
- """Clean content by removing leading dots and normalizing line endings"""
205
- if not content:
206
- return ''
207
- lines = content.splitlines()
208
- cleaned_lines = [line[1:] if line.startswith('.') else line for line in lines]
209
- return '\n'.join(cleaned_lines)
210
-
211
- def extract_instructions_section(response_text: str) -> Optional[str]:
212
- """Extract text between BEGIN_INSTRUCTIONS and END_INSTRUCTIONS markers using exact line matching"""
213
- try:
214
- lines = response_text.splitlines()
215
- start_marker = "BEGIN_INSTRUCTIONS"
216
- end_marker = "END_INSTRUCTIONS"
217
-
218
- # Find exact line matches for markers
219
- start_idx = None
220
- end_idx = None
221
-
222
- for i, line in enumerate(lines):
223
- line = line.strip()
224
- if line == start_marker and start_idx is None:
225
- start_idx = i
226
- continue
227
- if line == end_marker and start_idx is not None:
228
- end_idx = i
229
- break
230
-
231
- if start_idx is None or end_idx is None:
232
- if config.debug:
233
- if start_idx is None:
234
- console.print("[yellow]BEGIN_CHANGES marker not found[/yellow]")
235
- else:
236
- console.print("[yellow]END_CHANGES marker not found[/yellow]")
237
- return None
238
-
239
- # Extract lines between markers (exclusive)
240
- changes_text = '\n'.join(lines[start_idx + 1:end_idx])
241
- if not changes_text.strip():
242
- if config.debug:
243
- console.print("[yellow]Empty changes section found[/yellow]")
244
- return None
245
-
246
- return changes_text.strip()
247
-
248
- except Exception as e:
249
- console.print(f"[red]Error extracting changes section: {e}[/red]")
250
- return None
251
-
252
- def parse_response(response_text: str) -> List[FileChange]:
253
- """Parse a response string into FileChange objects"""
254
- parser = CommandParser()
255
- statement_parser = StatementParser()
256
-
257
- # First extract the changes section
258
- instructions = extract_instructions_section(response_text)
259
- if not instructions:
260
- if config.debug:
261
- console.print("[yellow]No changes section found in response[/yellow]")
262
- return []
263
-
264
- statements = statement_parser.parse(instructions)
265
- return parser.parse_statements(statements)
266
-
267
- def build_change_request_prompt(
268
- option_text: str,
269
- request: str,
270
- ) -> str:
271
- """Build prompt for change request details
272
-
273
- Args:
274
- option_text: Formatted text describing the selected option
275
- request: The original user request
276
- files_content_xml: Content of relevant files in XML format
277
-
278
- Returns:
279
- Formatted prompt string
280
- """
281
- short_uuid = str(uuid.uuid4())[:8]
282
-
283
- return CHANGE_REQUEST_PROMPT.format(
284
- option_text=option_text,
285
- request=request,
286
- uuid=short_uuid
287
- )
janito/change/play.py DELETED
@@ -1,54 +0,0 @@
1
- from pathlib import Path
2
- from typing import Tuple, Optional
3
- from rich.console import Console
4
- from rich.prompt import Confirm
5
- from .parser import parse_response
6
- from .preview import setup_workspace_dir_preview
7
- from .applier.main import ChangeApplier
8
- from .viewer import preview_all_changes # Add this import
9
- from ..config import config # Add this import
10
-
11
-
12
- def play_saved_changes(history_file: Path) -> Tuple[bool, Optional[Path]]:
13
- """
14
- Replay changes from a saved history file
15
- Returns:
16
- success: True if changes were applied successfully
17
- history_file: Path to the history file that was played
18
- """
19
- console = Console()
20
-
21
- if not history_file.exists():
22
- console.print(f"[red]History file not found: {history_file}[/red]")
23
- return False, None
24
-
25
- content = history_file.read_text()
26
- changes = parse_response(content)
27
-
28
- if not changes:
29
- console.print("[yellow]No changes found in history file[/yellow]")
30
- return False, None
31
-
32
-
33
- # Create preview directory and apply changes
34
- _, preview_dir = setup_workspace_dir_preview()
35
- applier = ChangeApplier(preview_dir)
36
-
37
- success, _ = applier.apply_changes(changes, debug=True)
38
- if success:
39
- preview_all_changes(console, changes)
40
-
41
- if not config.auto_apply:
42
- apply_changes = Confirm.ask("[cyan]Apply changes to working dir?[/cyan]", default=False)
43
- else:
44
- apply_changes = True
45
- console.print("[cyan]Auto-applying changes to working dir...[/cyan]")
46
-
47
- if apply_changes:
48
- applier.apply_to_workspace_dir(changes)
49
- console.print("[green]Changes applied successfully[/green]")
50
- else:
51
- console.print("[yellow]Changes were not applied[/yellow]")
52
- return False, history_file
53
-
54
- return success, history_file
janito/change/preview.py DELETED
@@ -1,82 +0,0 @@
1
- from pathlib import Path
2
- import shutil
3
- import tempfile
4
- from typing import List, Set, Tuple
5
- from datetime import datetime
6
- from rich.console import Console
7
- from rich.panel import Panel
8
-
9
- from janito.config import config
10
-
11
- def create_backup() -> Path:
12
- """Create backup directory and restore script.
13
- Returns the path to the backup directory."""
14
- backup_dir = config.workspace_dir / '.janito' / 'backups' / datetime.now().strftime('%Y%m%d_%H%M%S')
15
- backup_dir.parent.mkdir(parents=True, exist_ok=True)
16
-
17
- # Copy existing files to backup directory
18
- if config.workspace_dir.exists():
19
- shutil.copytree(config.workspace_dir, backup_dir, ignore=shutil.ignore_patterns('.janito', '.git'))
20
-
21
- # Create restore script
22
- restore_script = config.workspace_dir / '.janito' / 'restore.sh'
23
- restore_script.parent.mkdir(parents=True, exist_ok=True)
24
- script_content = f"""#!/bin/bash
25
- # Restore script generated by Janito
26
- # Restores files from backup created at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
27
-
28
- # Exit on error
29
- set -e
30
-
31
- # Get backup directory from argument or use latest
32
- BACKUP_DIR="$1"
33
- if [ -z "$BACKUP_DIR" ]; then
34
- BACKUP_DIR="{backup_dir}"
35
- echo "No backup directory specified, using latest: $BACKUP_DIR"
36
- fi
37
-
38
- # Show usage if --help is provided
39
- if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
40
- echo "Usage: $0 [backup_directory]"
41
- echo ""
42
- echo "If no backup directory is provided, uses the latest backup at:"
43
- echo "{backup_dir}"
44
- exit 0
45
- fi
46
-
47
- # Check if backup directory exists
48
- if [ ! -d "$BACKUP_DIR" ]; then
49
- echo "Error: Backup directory not found at $BACKUP_DIR"
50
- exit 1
51
- fi
52
-
53
- # Restore files from backup
54
- echo "Restoring files from backup..."
55
- cp -r "$BACKUP_DIR"/* "{config.workspace_dir}/"
56
-
57
- echo "Files restored successfully from $BACKUP_DIR"
58
- """
59
- restore_script.write_text(script_content)
60
- restore_script.chmod(0o755)
61
-
62
- return backup_dir
63
-
64
- def setup_preview_directory() -> Path:
65
- """Creates and sets up preview directory with working directory contents.
66
- Returns the path to the preview directory."""
67
- preview_dir = Path(tempfile.mkdtemp())
68
-
69
- # Copy existing files to preview directory if workspace_dir exists
70
- if config.workspace_dir.exists():
71
- shutil.copytree(config.workspace_dir, preview_dir, dirs_exist_ok=True,
72
- ignore=shutil.ignore_patterns('.janito', '.git'))
73
-
74
- return preview_dir
75
-
76
- def setup_workspace_dir_preview() -> tuple[Path, Path]:
77
- """Sets up both backup and preview directories.
78
- Returns (backup_dir, preview_dir) tuple."""
79
- backup_dir = create_backup()
80
- preview_dir = setup_preview_directory()
81
- return backup_dir, preview_dir
82
-