janito 0.6.0__py3-none-any.whl → 0.8.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.
- janito/__main__.py +127 -134
- janito/agents/__init__.py +22 -16
- janito/agents/agent.py +24 -20
- janito/agents/claudeai.py +41 -55
- janito/agents/deepseekai.py +47 -0
- janito/change/applied_blocks.py +34 -0
- janito/change/applier.py +167 -0
- janito/change/edit_blocks.py +148 -0
- janito/change/finder.py +72 -0
- janito/change/request.py +144 -0
- janito/change/validator.py +87 -251
- janito/change/view/content.py +63 -0
- janito/change/{viewer → view}/diff.py +44 -43
- janito/change/view/panels.py +201 -0
- janito/change/view/sections.py +69 -0
- janito/change/view/styling.py +140 -0
- janito/change/view/summary.py +37 -0
- janito/change/{viewer → view}/themes.py +62 -55
- janito/change/view/viewer.py +59 -0
- janito/cli/__init__.py +1 -1
- janito/cli/commands.py +68 -45
- janito/cli/functions.py +66 -111
- janito/common.py +132 -53
- janito/config.py +99 -101
- janito/data/change_prompt.txt +81 -0
- janito/data/system_prompt.txt +3 -0
- janito/qa.py +56 -66
- janito/version.py +22 -22
- janito/workspace/__init__.py +8 -7
- janito/workspace/analysis.py +120 -120
- janito/workspace/models.py +97 -0
- janito/workspace/show.py +115 -0
- janito/workspace/stats.py +42 -0
- janito/workspace/workset.py +135 -0
- janito/workspace/workspace.py +335 -0
- janito-0.8.0.dist-info/METADATA +106 -0
- janito-0.8.0.dist-info/RECORD +40 -0
- {janito-0.6.0.dist-info → janito-0.8.0.dist-info}/licenses/LICENSE +20 -20
- janito/__init__.py +0 -2
- janito/agents/openai.py +0 -53
- janito/agents/test.py +0 -34
- janito/change/__init__.py +0 -32
- janito/change/__main__.py +0 -0
- janito/change/analysis/__init__.py +0 -23
- janito/change/analysis/__main__.py +0 -7
- janito/change/analysis/analyze.py +0 -61
- janito/change/analysis/formatting.py +0 -78
- janito/change/analysis/options.py +0 -81
- janito/change/analysis/prompts.py +0 -98
- janito/change/analysis/view/__init__.py +0 -9
- janito/change/analysis/view/terminal.py +0 -171
- janito/change/applier/__init__.py +0 -5
- janito/change/applier/file.py +0 -58
- janito/change/applier/main.py +0 -156
- janito/change/applier/text.py +0 -245
- janito/change/applier/workspace_dir.py +0 -58
- janito/change/core.py +0 -131
- janito/change/history.py +0 -44
- janito/change/operations.py +0 -7
- janito/change/parser.py +0 -289
- janito/change/play.py +0 -54
- janito/change/preview.py +0 -82
- janito/change/prompts.py +0 -126
- janito/change/test.py +0 -0
- janito/change/viewer/__init__.py +0 -11
- janito/change/viewer/content.py +0 -66
- janito/change/viewer/pager.py +0 -56
- janito/change/viewer/panels.py +0 -555
- janito/change/viewer/styling.py +0 -103
- janito/clear_statement_parser/clear_statement_format.txt +0 -328
- janito/clear_statement_parser/examples.txt +0 -326
- janito/clear_statement_parser/models.py +0 -104
- janito/clear_statement_parser/parser.py +0 -496
- janito/cli/base.py +0 -30
- janito/cli/handlers/ask.py +0 -22
- janito/cli/handlers/demo.py +0 -22
- janito/cli/handlers/request.py +0 -24
- janito/cli/handlers/scan.py +0 -9
- janito/cli/history.py +0 -61
- janito/cli/registry.py +0 -26
- janito/demo/__init__.py +0 -4
- janito/demo/data.py +0 -13
- janito/demo/mock_data.py +0 -20
- janito/demo/operations.py +0 -45
- janito/demo/runner.py +0 -59
- janito/demo/scenarios.py +0 -32
- janito/prompts.py +0 -2
- janito/review.py +0 -13
- janito/search_replace/README.md +0 -146
- janito/search_replace/__init__.py +0 -6
- janito/search_replace/__main__.py +0 -21
- janito/search_replace/core.py +0 -119
- janito/search_replace/parser.py +0 -52
- janito/search_replace/play.py +0 -61
- janito/search_replace/replacer.py +0 -36
- janito/search_replace/searcher.py +0 -299
- janito/shell/__init__.py +0 -39
- janito/shell/bus.py +0 -31
- janito/shell/commands.py +0 -195
- janito/shell/handlers.py +0 -122
- janito/shell/history.py +0 -20
- janito/shell/processor.py +0 -52
- janito/tui/__init__.py +0 -21
- janito/tui/base.py +0 -22
- janito/tui/flows/__init__.py +0 -5
- janito/tui/flows/changes.py +0 -65
- janito/tui/flows/content.py +0 -128
- janito/tui/flows/selection.py +0 -117
- janito/tui/screens/__init__.py +0 -3
- janito/tui/screens/app.py +0 -1
- janito/workspace/manager.py +0 -48
- janito/workspace/scan.py +0 -232
- janito-0.6.0.dist-info/METADATA +0 -185
- janito-0.6.0.dist-info/RECORD +0 -95
- {janito-0.6.0.dist-info → janito-0.8.0.dist-info}/WHEEL +0 -0
- {janito-0.6.0.dist-info → janito-0.8.0.dist-info}/entry_points.txt +0 -0
@@ -1,61 +0,0 @@
|
|
1
|
-
"""Core analysis functionality."""
|
2
|
-
|
3
|
-
from typing import Optional, Dict
|
4
|
-
|
5
|
-
from janito.agents import agent
|
6
|
-
from janito.common import progress_send_message
|
7
|
-
from janito.config import config
|
8
|
-
from .view import format_analysis
|
9
|
-
from .options import AnalysisOption, parse_analysis_options
|
10
|
-
from .prompts import (
|
11
|
-
build_request_analysis_prompt,
|
12
|
-
get_option_selection,
|
13
|
-
validate_option_letter
|
14
|
-
)
|
15
|
-
|
16
|
-
def analyze_request(
|
17
|
-
request: str,
|
18
|
-
files_content_xml: str,
|
19
|
-
pre_select: str = ""
|
20
|
-
) -> Optional[AnalysisOption]:
|
21
|
-
"""
|
22
|
-
Analyze changes and get user selection.
|
23
|
-
|
24
|
-
Args:
|
25
|
-
files_content: Content of files to analyze
|
26
|
-
request: User's change request
|
27
|
-
pre_select: Optional pre-selected option letter
|
28
|
-
|
29
|
-
Returns:
|
30
|
-
Selected AnalysisOption or None if modified
|
31
|
-
"""
|
32
|
-
# Build and send prompt
|
33
|
-
prompt = build_request_analysis_prompt(request, files_content_xml)
|
34
|
-
response = progress_send_message(prompt)
|
35
|
-
|
36
|
-
# Parse options
|
37
|
-
options = parse_analysis_options(response)
|
38
|
-
if not options:
|
39
|
-
return None
|
40
|
-
|
41
|
-
if pre_select:
|
42
|
-
return options.get(pre_select.upper())
|
43
|
-
|
44
|
-
if config.tui:
|
45
|
-
from janito.tui import TuiApp
|
46
|
-
app = TuiApp(options=options)
|
47
|
-
app.run()
|
48
|
-
return app.selected_option
|
49
|
-
|
50
|
-
# Display formatted analysis in terminal mode
|
51
|
-
format_analysis(response, config.raw)
|
52
|
-
|
53
|
-
# Get user selection
|
54
|
-
while True:
|
55
|
-
selection = get_option_selection()
|
56
|
-
|
57
|
-
if selection == 'M':
|
58
|
-
return None
|
59
|
-
|
60
|
-
if validate_option_letter(selection, options):
|
61
|
-
return options[selection.upper()]
|
@@ -1,78 +0,0 @@
|
|
1
|
-
"""Centralized formatting utilities for analysis display."""
|
2
|
-
|
3
|
-
from typing import Dict, List, Text
|
4
|
-
from rich.text import Text
|
5
|
-
from rich.columns import Columns
|
6
|
-
from rich.padding import Padding
|
7
|
-
from pathlib import Path
|
8
|
-
|
9
|
-
# Layout constants
|
10
|
-
COLUMN_SPACING = 6 # Increased spacing between columns
|
11
|
-
MIN_PANEL_WIDTH = 45 # Wider minimum width for better readability
|
12
|
-
SECTION_PADDING = (2, 0) # More vertical padding
|
13
|
-
|
14
|
-
# Color scheme constants
|
15
|
-
STATUS_COLORS = {
|
16
|
-
'new': 'bright_green',
|
17
|
-
'modified': 'yellow',
|
18
|
-
'removed': 'red',
|
19
|
-
'default': 'white'
|
20
|
-
}
|
21
|
-
|
22
|
-
STRUCTURAL_COLORS = {
|
23
|
-
'directory': 'dim',
|
24
|
-
'separator': 'blue dim',
|
25
|
-
'repeat': 'bright_magenta bold'
|
26
|
-
}
|
27
|
-
|
28
|
-
def create_header(text: str, style: str = "bold cyan") -> Text:
|
29
|
-
"""Create formatted header with separator."""
|
30
|
-
content = Text()
|
31
|
-
content.append(text, style=style)
|
32
|
-
content.append("\n")
|
33
|
-
content.append("═" * len(text), style="cyan")
|
34
|
-
return content
|
35
|
-
|
36
|
-
def create_section_header(text: str, width: int = 20) -> Text:
|
37
|
-
"""Create centered section header with separator."""
|
38
|
-
content = Text()
|
39
|
-
padding = (width - len(text)) // 2
|
40
|
-
content.append(" " * padding + text, style="bold cyan")
|
41
|
-
content.append("\n")
|
42
|
-
content.append("─" * width, style="cyan")
|
43
|
-
return content
|
44
|
-
|
45
|
-
def format_file_path(path: str, status: str, max_dir_length: int = 0, is_repeated: bool = False) -> Text:
|
46
|
-
"""Format file path with status indicators and consistent alignment.
|
47
|
-
|
48
|
-
Args:
|
49
|
-
path: File path to format
|
50
|
-
status: File status (Modified, New, Removed)
|
51
|
-
max_dir_length: Maximum directory name length for padding
|
52
|
-
is_repeated: Whether this directory was seen before
|
53
|
-
"""
|
54
|
-
content = Text()
|
55
|
-
style = STATUS_COLORS.get(status.lower(), STATUS_COLORS['default'])
|
56
|
-
|
57
|
-
parts = Path(path).parts
|
58
|
-
parent_dir = str(Path(path).parent)
|
59
|
-
|
60
|
-
if parent_dir != '.':
|
61
|
-
# Add 4 spaces for consistent base padding
|
62
|
-
base_padding = 4
|
63
|
-
dir_padding = max_dir_length + base_padding
|
64
|
-
|
65
|
-
if is_repeated:
|
66
|
-
# Add arrow with consistent spacing
|
67
|
-
content.append(" " * base_padding)
|
68
|
-
content.append("↑ ", style="magenta")
|
69
|
-
content.append(" " * (dir_padding - base_padding - 2))
|
70
|
-
else:
|
71
|
-
# Left-align directory name with consistent padding
|
72
|
-
content.append(" " * base_padding)
|
73
|
-
content.append(parent_dir, style="dim")
|
74
|
-
content.append(" " * (dir_padding - len(parent_dir) - base_padding))
|
75
|
-
|
76
|
-
# Add filename with consistent spacing
|
77
|
-
content.append(parts[-1], style=style)
|
78
|
-
return content
|
@@ -1,81 +0,0 @@
|
|
1
|
-
from dataclasses import dataclass, field
|
2
|
-
from typing import Dict, List
|
3
|
-
from pathlib import Path
|
4
|
-
|
5
|
-
@dataclass
|
6
|
-
class AnalysisOption:
|
7
|
-
"""Represents an analysis option with letter identifier and details"""
|
8
|
-
letter: str
|
9
|
-
summary: str
|
10
|
-
description_items: List[str] = field(default_factory=list)
|
11
|
-
affected_files: List[str] = field(default_factory=list)
|
12
|
-
|
13
|
-
def format_option_text(self) -> str:
|
14
|
-
"""Format option details as text for change core"""
|
15
|
-
text = f"Option {self.letter} - {self.summary}\n"
|
16
|
-
text += "=" * len(f"Option {self.letter} - {self.summary}") + "\n\n"
|
17
|
-
|
18
|
-
if self.description_items:
|
19
|
-
text += "Description:\n"
|
20
|
-
for item in self.description_items:
|
21
|
-
text += f"- {item}\n"
|
22
|
-
text += "\n"
|
23
|
-
|
24
|
-
if self.affected_files:
|
25
|
-
text += "Affected files:\n"
|
26
|
-
for file in self.affected_files:
|
27
|
-
text += f"- {file}\n"
|
28
|
-
|
29
|
-
return text
|
30
|
-
|
31
|
-
def is_new_directory(self, file_path: str) -> bool:
|
32
|
-
"""Check if file path represents the first occurrence of a directory"""
|
33
|
-
parent = str(Path(file_path).parent)
|
34
|
-
return parent != '.' and not any(
|
35
|
-
parent in self.get_clean_path(file)
|
36
|
-
for file in self.affected_files
|
37
|
-
if self.get_clean_path(file) != file_path
|
38
|
-
)
|
39
|
-
|
40
|
-
def get_clean_path(self, file_path: str) -> str:
|
41
|
-
"""Remove status markers from file path"""
|
42
|
-
return file_path.split(' (')[0].strip()
|
43
|
-
|
44
|
-
def parse_analysis_options(content: str) -> Dict[str, AnalysisOption]:
|
45
|
-
"""Parse analysis options from formatted text file"""
|
46
|
-
options = {}
|
47
|
-
current_option = None
|
48
|
-
current_section = None
|
49
|
-
|
50
|
-
for line in content.splitlines():
|
51
|
-
line = line.strip()
|
52
|
-
|
53
|
-
# Skip empty lines and section separators
|
54
|
-
if not line or line.startswith('---') or line == 'END_OF_OPTIONS':
|
55
|
-
continue
|
56
|
-
|
57
|
-
# New option starts with a letter and period
|
58
|
-
if line[0].isalpha() and line[1:3] == '. ':
|
59
|
-
letter, summary = line.split('. ', 1)
|
60
|
-
current_option = AnalysisOption(letter=letter.upper(), summary=summary)
|
61
|
-
options[letter.upper()] = current_option
|
62
|
-
current_section = None
|
63
|
-
continue
|
64
|
-
|
65
|
-
# Section headers
|
66
|
-
if line.lower() == 'description:':
|
67
|
-
current_section = 'description'
|
68
|
-
continue
|
69
|
-
elif line.lower() == 'affected files:':
|
70
|
-
current_section = 'files'
|
71
|
-
continue
|
72
|
-
|
73
|
-
# Add items to current section
|
74
|
-
if current_option and line.startswith('- '):
|
75
|
-
content = line[2:].strip()
|
76
|
-
if current_section == 'description':
|
77
|
-
current_option.description_items.append(content)
|
78
|
-
elif current_section == 'files':
|
79
|
-
current_option.affected_files.append(content)
|
80
|
-
|
81
|
-
return options
|
@@ -1,98 +0,0 @@
|
|
1
|
-
"""User prompts and input handling for analysis."""
|
2
|
-
|
3
|
-
from typing import List, Dict
|
4
|
-
from rich.console import Console
|
5
|
-
from rich.panel import Panel
|
6
|
-
from rich.rule import Rule
|
7
|
-
from rich.prompt import Prompt
|
8
|
-
from rich import box
|
9
|
-
|
10
|
-
|
11
|
-
from .options import AnalysisOption
|
12
|
-
|
13
|
-
# Keep only prompt-related functionality
|
14
|
-
CHANGE_ANALYSIS_PROMPT = """
|
15
|
-
Current files:
|
16
|
-
<files>
|
17
|
-
{files_content}
|
18
|
-
</files>
|
19
|
-
|
20
|
-
Considering the above current files content, provide 3 sections, each identified by a keyword and representing an option.
|
21
|
-
Each option should include a concise description and a list of affected files.
|
22
|
-
1st option should be basic style change, 2nd organized style, 3rd exntensible style.
|
23
|
-
Do not use style as keyword, instead focus on the changes summary.
|
24
|
-
|
25
|
-
Use the following format:
|
26
|
-
|
27
|
-
A. Keyword summary of the change
|
28
|
-
-----------------
|
29
|
-
Description:
|
30
|
-
- Concise description of the change
|
31
|
-
|
32
|
-
Affected files:
|
33
|
-
- path/file1.py (new)
|
34
|
-
- path/file2.py (modified)
|
35
|
-
- path/file3.py (removed)
|
36
|
-
|
37
|
-
END_OF_OPTIONS (mandatory marker)
|
38
|
-
|
39
|
-
RULES:
|
40
|
-
- do NOT provide the content of the files
|
41
|
-
- do NOT offer to implement the changes
|
42
|
-
- description items should be 80 chars or less
|
43
|
-
|
44
|
-
Request:
|
45
|
-
{request}
|
46
|
-
"""
|
47
|
-
|
48
|
-
def prompt_user(message: str, choices: List[str] = None) -> str:
|
49
|
-
"""Display a prominent user prompt with optional choices"""
|
50
|
-
console = Console()
|
51
|
-
term_width = console.width or 80
|
52
|
-
console.print()
|
53
|
-
console.print(Rule(" User Input Required ", style="bold cyan", align="center"))
|
54
|
-
|
55
|
-
if choices:
|
56
|
-
choice_text = f"[cyan]Options: {', '.join(choices)}[/cyan]"
|
57
|
-
console.print(Panel(choice_text, box=box.ROUNDED, justify="center"))
|
58
|
-
|
59
|
-
# Center the prompt with padding
|
60
|
-
padding = (term_width - len(message)) // 2
|
61
|
-
padded_message = " " * padding + message
|
62
|
-
return Prompt.ask(f"[bold cyan]{padded_message}[/bold cyan]")
|
63
|
-
|
64
|
-
def validate_option_letter(letter: str, options: Dict[str, AnalysisOption]) -> bool:
|
65
|
-
"""Validate if the given letter is a valid option or 'M' for modify"""
|
66
|
-
if letter.upper() == 'M':
|
67
|
-
return True
|
68
|
-
return letter.upper() in options
|
69
|
-
|
70
|
-
def get_option_selection() -> str:
|
71
|
-
"""Get user input for option selection with modify option"""
|
72
|
-
console = Console()
|
73
|
-
term_width = console.width or 80
|
74
|
-
message = "Enter option letter or 'M' to modify request"
|
75
|
-
padding = (term_width - len(message)) // 2
|
76
|
-
padded_message = " " * padding + message
|
77
|
-
|
78
|
-
console.print(f"\n[cyan]{padded_message}[/cyan]")
|
79
|
-
while True:
|
80
|
-
letter = prompt_user("Select option").strip().upper()
|
81
|
-
if letter == 'M' or (letter.isalpha() and len(letter) == 1):
|
82
|
-
return letter
|
83
|
-
|
84
|
-
error_msg = "Please enter a valid letter or 'M'"
|
85
|
-
error_padding = (term_width - len(error_msg)) // 2
|
86
|
-
padded_error = " " * error_padding + error_msg
|
87
|
-
console.print(f"[red]{padded_error}[/red]")
|
88
|
-
|
89
|
-
def build_request_analysis_prompt(request: str, files_content_xml: str) -> str:
|
90
|
-
"""Build prompt for information requests"""
|
91
|
-
return CHANGE_ANALYSIS_PROMPT.format(
|
92
|
-
files_content=files_content_xml,
|
93
|
-
request=request
|
94
|
-
)
|
95
|
-
|
96
|
-
def build_request_analysis_prompt(request: str, files_content_xml: str) -> str:
|
97
|
-
"""Build analysis prompt with minimal formatting."""
|
98
|
-
return f"Current files:\n{files_content_xml}\n\nRequest:\n{request}"
|
@@ -1,171 +0,0 @@
|
|
1
|
-
"""Terminal UI components for analysis display."""
|
2
|
-
|
3
|
-
from typing import Dict, List, Optional
|
4
|
-
from rich.console import Console
|
5
|
-
from rich.columns import Columns
|
6
|
-
from rich.text import Text
|
7
|
-
from rich.panel import Panel
|
8
|
-
from rich.rule import Rule
|
9
|
-
from rich.padding import Padding
|
10
|
-
from rich.prompt import Prompt
|
11
|
-
from rich import box
|
12
|
-
from pathlib import Path
|
13
|
-
|
14
|
-
from ..options import AnalysisOption
|
15
|
-
from ..formatting import (
|
16
|
-
COLUMN_SPACING,
|
17
|
-
MIN_PANEL_WIDTH,
|
18
|
-
SECTION_PADDING,
|
19
|
-
STATUS_COLORS,
|
20
|
-
STRUCTURAL_COLORS,
|
21
|
-
create_header,
|
22
|
-
create_section_header,
|
23
|
-
format_file_path
|
24
|
-
)
|
25
|
-
|
26
|
-
def prompt_user(message: str, choices: List[str] = None) -> str:
|
27
|
-
"""Display a prominent user prompt with optional choices"""
|
28
|
-
console = Console()
|
29
|
-
term_width = console.width or 80
|
30
|
-
console.print()
|
31
|
-
console.print(Rule(" User Input Required ", style="bold cyan", align="center"))
|
32
|
-
|
33
|
-
if choices:
|
34
|
-
choice_text = f"[cyan]Options: {', '.join(choices)}[/cyan]"
|
35
|
-
console.print(Panel(choice_text, box=box.ROUNDED, justify="center"))
|
36
|
-
|
37
|
-
padding = (term_width - len(message)) // 2
|
38
|
-
padded_message = " " * padding + message
|
39
|
-
return Prompt.ask(f"[bold cyan]{padded_message}[/bold cyan]")
|
40
|
-
|
41
|
-
def get_option_selection() -> str:
|
42
|
-
"""Get user input for option selection with modify option"""
|
43
|
-
console = Console()
|
44
|
-
term_width = console.width or 80
|
45
|
-
message = "Enter option letter or 'M' to modify request"
|
46
|
-
padding = (term_width - len(message)) // 2
|
47
|
-
padded_message = " " * padding + message
|
48
|
-
|
49
|
-
console.print(f"\n[cyan]{padded_message}[/cyan]")
|
50
|
-
while True:
|
51
|
-
letter = prompt_user("Select option").strip().upper()
|
52
|
-
if letter == 'M' or (letter.isalpha() and len(letter) == 1):
|
53
|
-
return letter
|
54
|
-
|
55
|
-
error_msg = "Please enter a valid letter or 'M'"
|
56
|
-
error_padding = (term_width - len(error_msg)) // 2
|
57
|
-
padded_error = " " * error_padding + error_msg
|
58
|
-
console.print(f"[red]{padded_error}[/red]")
|
59
|
-
|
60
|
-
def _create_option_content(option: AnalysisOption) -> Text:
|
61
|
-
"""Create rich formatted content for a single option."""
|
62
|
-
content = Text()
|
63
|
-
content.append("\n")
|
64
|
-
|
65
|
-
header = create_header(f"Option {option.letter} - {option.summary}")
|
66
|
-
content.append(header)
|
67
|
-
content.append("\n")
|
68
|
-
|
69
|
-
if option.description_items:
|
70
|
-
for item in option.description_items:
|
71
|
-
content.append("• ", style="cyan")
|
72
|
-
content.append(f"{item}\n")
|
73
|
-
content.append("\n")
|
74
|
-
|
75
|
-
# Add consistent padding before file list
|
76
|
-
content.append("\n" * 2)
|
77
|
-
|
78
|
-
if option.affected_files:
|
79
|
-
files = {status: [] for status in ['New', 'Modified', 'Removed']}
|
80
|
-
for file in option.affected_files:
|
81
|
-
if '(new)' in file.lower():
|
82
|
-
files['New'].append(file)
|
83
|
-
elif '(removed)' in file.lower():
|
84
|
-
files['Removed'].append(file)
|
85
|
-
else:
|
86
|
-
files['Modified'].append(file)
|
87
|
-
|
88
|
-
for status, status_files in files.items():
|
89
|
-
if status_files:
|
90
|
-
content.append(create_section_header(f"{status} Files"))
|
91
|
-
content.append("\n")
|
92
|
-
sorted_files = sorted(status_files)
|
93
|
-
prev_path = None
|
94
|
-
seen_dirs = {}
|
95
|
-
for file in sorted_files:
|
96
|
-
path = option.get_clean_path(file)
|
97
|
-
current_parts = Path(path).parts
|
98
|
-
parent_dir = str(Path(path).parent)
|
99
|
-
|
100
|
-
if parent_dir != '.':
|
101
|
-
is_repeated = parent_dir in seen_dirs
|
102
|
-
if not is_repeated:
|
103
|
-
content.append(parent_dir, style=STRUCTURAL_COLORS['directory'])
|
104
|
-
content.append("/", style=STRUCTURAL_COLORS['separator'])
|
105
|
-
seen_dirs[parent_dir] = True
|
106
|
-
else:
|
107
|
-
padding = " " * (len(parent_dir) - 1)
|
108
|
-
content.append(padding)
|
109
|
-
content.append("↑ ", style=STRUCTURAL_COLORS['repeat'])
|
110
|
-
content.append("/", style=STRUCTURAL_COLORS['separator'])
|
111
|
-
content.append(current_parts[-1], style=STATUS_COLORS[status.lower()])
|
112
|
-
else:
|
113
|
-
content.append(current_parts[-1], style=STATUS_COLORS[status.lower()])
|
114
|
-
content.append("\n")
|
115
|
-
content.append("\n")
|
116
|
-
|
117
|
-
content.append("\n")
|
118
|
-
|
119
|
-
return content
|
120
|
-
|
121
|
-
def create_columns_layout(options_content: List[Text], term_width: int) -> Columns:
|
122
|
-
"""Create a columns layout with consistent spacing."""
|
123
|
-
num_columns = len(options_content)
|
124
|
-
spacing = COLUMN_SPACING * (num_columns - 1)
|
125
|
-
safety_margin = 4 + 2
|
126
|
-
|
127
|
-
usable_width = term_width - spacing - safety_margin
|
128
|
-
column_width = max((usable_width // num_columns), MIN_PANEL_WIDTH)
|
129
|
-
|
130
|
-
rendered_columns = [
|
131
|
-
Padding(content, (0, COLUMN_SPACING // 2))
|
132
|
-
for content in options_content
|
133
|
-
]
|
134
|
-
|
135
|
-
return Columns(
|
136
|
-
rendered_columns,
|
137
|
-
equal=True,
|
138
|
-
expand=True,
|
139
|
-
width=column_width,
|
140
|
-
align="left",
|
141
|
-
padding=(0, 0),
|
142
|
-
)
|
143
|
-
|
144
|
-
def format_analysis(analysis: str, raw: bool = False) -> None:
|
145
|
-
"""Format and display the analysis output."""
|
146
|
-
from ..options import parse_analysis_options
|
147
|
-
|
148
|
-
console = Console()
|
149
|
-
term_width = console.width or 100
|
150
|
-
|
151
|
-
if raw:
|
152
|
-
console.print(analysis)
|
153
|
-
return
|
154
|
-
|
155
|
-
options = parse_analysis_options(analysis)
|
156
|
-
if not options:
|
157
|
-
console.print("\n[yellow]Warning: No valid options found in response.[/yellow]\n")
|
158
|
-
console.print(analysis)
|
159
|
-
return
|
160
|
-
|
161
|
-
columns_content = [_create_option_content(options[letter])
|
162
|
-
for letter in sorted(options.keys())]
|
163
|
-
|
164
|
-
columns = create_columns_layout(columns_content, term_width)
|
165
|
-
|
166
|
-
console.print("\n")
|
167
|
-
console.print(Text("Analysis Options", style="bold cyan"))
|
168
|
-
console.print(Text("─" * term_width, style="cyan dim"))
|
169
|
-
console.print(columns)
|
170
|
-
console.print(Text("─" * term_width, style="cyan dim"))
|
171
|
-
console.print("\n")
|
janito/change/applier/file.py
DELETED
@@ -1,58 +0,0 @@
|
|
1
|
-
from pathlib import Path
|
2
|
-
from typing import Tuple, Optional
|
3
|
-
from rich.console import Console
|
4
|
-
from ..parser import FileChange, ChangeOperation
|
5
|
-
|
6
|
-
class FileChangeApplier:
|
7
|
-
def __init__(self, preview_dir: Path, console: Console = None):
|
8
|
-
self.preview_dir = preview_dir
|
9
|
-
self.console = console or Console()
|
10
|
-
|
11
|
-
def apply_file_operation(self, change: FileChange) -> Tuple[bool, Optional[str]]:
|
12
|
-
"""Apply a file operation (create/replace/remove/rename/move)
|
13
|
-
Returns: (success, error_message)"""
|
14
|
-
path = self.preview_dir / change.name
|
15
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
16
|
-
|
17
|
-
# Store original content before any changes
|
18
|
-
if path.exists():
|
19
|
-
change.original_content = path.read_text()
|
20
|
-
|
21
|
-
if change.operation == ChangeOperation.REMOVE_FILE:
|
22
|
-
return self._handle_remove(path)
|
23
|
-
elif change.operation in (ChangeOperation.CREATE_FILE, ChangeOperation.REPLACE_FILE):
|
24
|
-
return self._handle_create_replace(path, change)
|
25
|
-
elif change.operation in (ChangeOperation.RENAME_FILE, ChangeOperation.MOVE_FILE):
|
26
|
-
return self._handle_move(path, change)
|
27
|
-
|
28
|
-
return False, f"Unsupported operation: {change.operation}"
|
29
|
-
|
30
|
-
def _handle_remove(self, path: Path) -> Tuple[bool, Optional[str]]:
|
31
|
-
"""Handle file removal"""
|
32
|
-
if path.exists():
|
33
|
-
path.unlink()
|
34
|
-
return True, None
|
35
|
-
|
36
|
-
def _handle_create_replace(self, path: Path, change: FileChange) -> Tuple[bool, Optional[str]]:
|
37
|
-
"""Handle file creation or replacement"""
|
38
|
-
if change.operation == ChangeOperation.CREATE_FILE and path.exists():
|
39
|
-
return False, f"Cannot create file {path} - already exists"
|
40
|
-
|
41
|
-
if change.content is not None:
|
42
|
-
path.write_text(change.content)
|
43
|
-
return True, None
|
44
|
-
|
45
|
-
return False, "No content provided for create/replace operation"
|
46
|
-
|
47
|
-
def _handle_move(self, path: Path, change: FileChange) -> Tuple[bool, Optional[str]]:
|
48
|
-
"""Handle file move/rename operations"""
|
49
|
-
if not path.exists():
|
50
|
-
return False, f"Cannot move/rename non-existent file {path}"
|
51
|
-
|
52
|
-
if not change.target:
|
53
|
-
return False, "No target path provided for move/rename operation"
|
54
|
-
|
55
|
-
new_path = self.preview_dir / change.target
|
56
|
-
new_path.parent.mkdir(parents=True, exist_ok=True)
|
57
|
-
path.rename(new_path)
|
58
|
-
return True, None
|