janito 0.4.0__py3-none-any.whl → 0.6.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/__init__.py +1 -1
- janito/__main__.py +102 -326
- janito/agents/__init__.py +16 -0
- janito/agents/agent.py +21 -0
- janito/{claude.py → agents/claudeai.py} +13 -17
- janito/agents/openai.py +53 -0
- janito/agents/test.py +34 -0
- janito/change/__init__.py +32 -0
- janito/change/__main__.py +0 -0
- janito/change/analysis/__init__.py +23 -0
- janito/change/analysis/__main__.py +7 -0
- janito/change/analysis/analyze.py +61 -0
- janito/change/analysis/formatting.py +78 -0
- janito/change/analysis/options.py +81 -0
- janito/change/analysis/prompts.py +98 -0
- janito/change/analysis/view/__init__.py +9 -0
- janito/change/analysis/view/terminal.py +171 -0
- janito/change/applier/__init__.py +5 -0
- janito/change/applier/file.py +58 -0
- janito/change/applier/main.py +156 -0
- janito/change/applier/text.py +245 -0
- janito/change/applier/workspace_dir.py +58 -0
- janito/change/core.py +131 -0
- janito/change/history.py +44 -0
- janito/change/operations.py +7 -0
- janito/change/parser.py +289 -0
- janito/change/play.py +54 -0
- janito/change/preview.py +82 -0
- janito/change/prompts.py +126 -0
- janito/change/test.py +0 -0
- janito/change/validator.py +251 -0
- janito/change/viewer/__init__.py +11 -0
- janito/change/viewer/content.py +66 -0
- janito/change/viewer/diff.py +43 -0
- janito/change/viewer/pager.py +56 -0
- janito/change/viewer/panels.py +555 -0
- janito/change/viewer/styling.py +103 -0
- janito/change/viewer/themes.py +55 -0
- janito/clear_statement_parser/clear_statement_format.txt +328 -0
- janito/clear_statement_parser/examples.txt +326 -0
- janito/clear_statement_parser/models.py +104 -0
- janito/clear_statement_parser/parser.py +496 -0
- janito/cli/__init__.py +2 -0
- janito/cli/base.py +30 -0
- janito/cli/commands.py +45 -0
- janito/cli/functions.py +111 -0
- janito/cli/handlers/ask.py +22 -0
- janito/cli/handlers/demo.py +22 -0
- janito/cli/handlers/request.py +24 -0
- janito/cli/handlers/scan.py +9 -0
- janito/cli/history.py +61 -0
- janito/cli/registry.py +26 -0
- janito/common.py +41 -10
- janito/config.py +71 -6
- janito/demo/__init__.py +4 -0
- janito/demo/data.py +13 -0
- janito/demo/mock_data.py +20 -0
- janito/demo/operations.py +45 -0
- janito/demo/runner.py +59 -0
- janito/demo/scenarios.py +32 -0
- janito/prompts.py +1 -65
- janito/qa.py +8 -5
- janito/review.py +13 -0
- janito/search_replace/README.md +146 -0
- janito/search_replace/__init__.py +6 -0
- janito/search_replace/__main__.py +21 -0
- janito/search_replace/core.py +119 -0
- janito/search_replace/parser.py +52 -0
- janito/search_replace/play.py +61 -0
- janito/search_replace/replacer.py +36 -0
- janito/search_replace/searcher.py +299 -0
- janito/shell/__init__.py +39 -0
- janito/shell/bus.py +31 -0
- janito/shell/commands.py +195 -0
- janito/shell/handlers.py +122 -0
- janito/shell/history.py +20 -0
- janito/shell/processor.py +52 -0
- janito/tui/__init__.py +21 -0
- janito/tui/base.py +22 -0
- janito/tui/flows/__init__.py +5 -0
- janito/tui/flows/changes.py +65 -0
- janito/tui/flows/content.py +128 -0
- janito/tui/flows/selection.py +117 -0
- janito/tui/screens/__init__.py +3 -0
- janito/tui/screens/app.py +1 -0
- janito/workspace/__init__.py +7 -0
- janito/workspace/analysis.py +121 -0
- janito/workspace/manager.py +48 -0
- janito/workspace/scan.py +232 -0
- janito-0.6.0.dist-info/METADATA +185 -0
- janito-0.6.0.dist-info/RECORD +95 -0
- {janito-0.4.0.dist-info → janito-0.6.0.dist-info}/WHEEL +1 -1
- janito/analysis.py +0 -281
- janito/changeapplier.py +0 -436
- janito/changeviewer.py +0 -350
- janito/console.py +0 -330
- janito/contentchange.py +0 -84
- janito/contextparser.py +0 -113
- janito/fileparser.py +0 -125
- janito/scan.py +0 -137
- janito-0.4.0.dist-info/METADATA +0 -164
- janito-0.4.0.dist-info/RECORD +0 -21
- {janito-0.4.0.dist-info → janito-0.6.0.dist-info}/entry_points.txt +0 -0
- {janito-0.4.0.dist-info → janito-0.6.0.dist-info}/licenses/LICENSE +0 -0
janito/cli/functions.py
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
import sys
|
2
|
+
import tempfile
|
3
|
+
from datetime import datetime, timezone
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import List, Optional
|
6
|
+
|
7
|
+
import typer
|
8
|
+
from rich.console import Console
|
9
|
+
from rich.markdown import Markdown
|
10
|
+
from rich.panel import Panel
|
11
|
+
from rich.prompt import Confirm, Prompt
|
12
|
+
from rich.text import Text
|
13
|
+
|
14
|
+
from janito.agents import AIAgent
|
15
|
+
from janito.common import progress_send_message
|
16
|
+
from janito.config import config
|
17
|
+
from janito.qa import ask_question, display_answer
|
18
|
+
from janito.workspace import collect_files_content
|
19
|
+
|
20
|
+
|
21
|
+
def prompt_user(message: str, choices: List[str] = None) -> str:
|
22
|
+
"""Display a simple user prompt with optional choices"""
|
23
|
+
console = Console()
|
24
|
+
|
25
|
+
if choices:
|
26
|
+
console.print(f"\n[cyan]Options: {', '.join(choices)}[/cyan]")
|
27
|
+
|
28
|
+
return Prompt.ask(f"[bold cyan]> {message}[/bold cyan]")
|
29
|
+
|
30
|
+
def validate_option_letter(letter: str, options: dict) -> bool:
|
31
|
+
"""Validate if the given letter is a valid option or 'M' for modify"""
|
32
|
+
return letter.upper() in options or letter.upper() == 'M'
|
33
|
+
|
34
|
+
def get_option_selection() -> str:
|
35
|
+
"""Get user input for option selection"""
|
36
|
+
console = Console()
|
37
|
+
console.print("\n[cyan]Enter option letter or 'M' to modify request[/cyan]")
|
38
|
+
|
39
|
+
while True:
|
40
|
+
letter = Prompt.ask("[bold cyan]Select option[/bold cyan]").strip().upper()
|
41
|
+
if letter == 'M' or (letter.isalpha() and len(letter) == 1):
|
42
|
+
return letter
|
43
|
+
|
44
|
+
console.print("[red]Please enter a valid letter or 'M'[/red]")
|
45
|
+
|
46
|
+
def get_change_history_path() -> Path:
|
47
|
+
"""Create and return the changes history directory path"""
|
48
|
+
changes_history_dir = config.workspace_dir / '.janito' / 'change_history'
|
49
|
+
changes_history_dir.mkdir(parents=True, exist_ok=True)
|
50
|
+
return changes_history_dir
|
51
|
+
|
52
|
+
def get_timestamp() -> str:
|
53
|
+
"""Get current UTC timestamp in YMD_HMS format with leading zeros"""
|
54
|
+
return datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')
|
55
|
+
|
56
|
+
def save_prompt_to_file(prompt: str) -> Path:
|
57
|
+
"""Save prompt to a named temporary file that won't be deleted"""
|
58
|
+
temp_file = tempfile.NamedTemporaryFile(prefix='selected_', suffix='.txt', delete=False)
|
59
|
+
temp_path = Path(temp_file.name)
|
60
|
+
temp_path.write_text(prompt)
|
61
|
+
return temp_path
|
62
|
+
|
63
|
+
def save_to_file(content: str, prefix: str) -> Path:
|
64
|
+
"""Save content to a timestamped file in changes history directory"""
|
65
|
+
changes_history_dir = get_change_history_path()
|
66
|
+
timestamp = get_timestamp()
|
67
|
+
filename = f"{timestamp}_{prefix}.txt"
|
68
|
+
file_path = changes_history_dir / filename
|
69
|
+
file_path.write_text(content)
|
70
|
+
return file_path
|
71
|
+
|
72
|
+
def modify_request(request: str) -> str:
|
73
|
+
"""Display current request and get modified version with improved formatting"""
|
74
|
+
console = Console()
|
75
|
+
|
76
|
+
# Display current request in a panel with clear formatting
|
77
|
+
console.print("\n[bold cyan]Current Request:[/bold cyan]")
|
78
|
+
console.print(Panel(
|
79
|
+
Text(request, style="white"),
|
80
|
+
border_style="blue",
|
81
|
+
title="Previous Request",
|
82
|
+
padding=(1, 2)
|
83
|
+
))
|
84
|
+
|
85
|
+
# Get modified request with clear prompt
|
86
|
+
console.print("\n[bold cyan]Enter modified request below:[/bold cyan]")
|
87
|
+
console.print("[dim](Press Enter to submit, Ctrl+C to cancel)[/dim]")
|
88
|
+
try:
|
89
|
+
new_request = prompt_user("Modified request")
|
90
|
+
if not new_request.strip():
|
91
|
+
console.print("[yellow]No changes made, keeping original request[/yellow]")
|
92
|
+
return request
|
93
|
+
return new_request
|
94
|
+
except KeyboardInterrupt:
|
95
|
+
console.print("\n[yellow]Modification cancelled, keeping original request[/yellow]")
|
96
|
+
return request
|
97
|
+
|
98
|
+
|
99
|
+
def read_stdin() -> str:
|
100
|
+
"""Read input from stdin until EOF"""
|
101
|
+
console = Console()
|
102
|
+
console.print("[dim]Enter your input (press Ctrl+D when finished):[/dim]")
|
103
|
+
return sys.stdin.read().strip()
|
104
|
+
|
105
|
+
def process_question(question: str) -> None:
|
106
|
+
"""Process a question about the codebase"""
|
107
|
+
paths_to_scan = [config.workspace_dir] if not config.include else config.include
|
108
|
+
files_content = collect_files_content(paths_to_scan)
|
109
|
+
answer = ask_question(question, files_content)
|
110
|
+
display_answer(answer)
|
111
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
from typing import Optional
|
3
|
+
from rich.console import Console
|
4
|
+
from janito.config import config
|
5
|
+
from janito.qa import ask_question, display_answer
|
6
|
+
from janito.workspace.scan import collect_files_content
|
7
|
+
from ..base import BaseCLIHandler
|
8
|
+
|
9
|
+
class AskHandler(BaseCLIHandler):
|
10
|
+
def handle(self, question: str):
|
11
|
+
"""Process a question about the codebase"""
|
12
|
+
paths_to_scan = [config.workspace_dir] if not config.include else config.include
|
13
|
+
files_content = collect_files_content(paths_to_scan)
|
14
|
+
|
15
|
+
if config.tui:
|
16
|
+
answer = ask_question(question, files_content)
|
17
|
+
from janito.tui import TuiApp
|
18
|
+
app = TuiApp(content=answer)
|
19
|
+
app.run()
|
20
|
+
else:
|
21
|
+
answer = ask_question(question, files_content)
|
22
|
+
display_answer(answer)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
from rich.console import Console
|
2
|
+
from janito.demo import DemoRunner
|
3
|
+
from janito.demo.data import get_demo_scenarios
|
4
|
+
from ..base import BaseCLIHandler
|
5
|
+
|
6
|
+
class DemoHandler(BaseCLIHandler):
|
7
|
+
def handle(self):
|
8
|
+
"""Run demo scenarios"""
|
9
|
+
runner = DemoRunner()
|
10
|
+
|
11
|
+
# Add predefined scenarios
|
12
|
+
for scenario in get_demo_scenarios():
|
13
|
+
runner.add_scenario(scenario)
|
14
|
+
|
15
|
+
# Preview and run scenarios
|
16
|
+
self.console.print("\n[bold cyan]Demo Scenarios Preview:[/bold cyan]")
|
17
|
+
runner.preview_changes()
|
18
|
+
|
19
|
+
self.console.print("\n[bold cyan]Running Demo Scenarios:[/bold cyan]")
|
20
|
+
runner.run_all()
|
21
|
+
|
22
|
+
self.console.print("\n[green]Demo completed successfully![/green]")
|
@@ -0,0 +1,24 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
from rich.console import Console
|
3
|
+
from janito.config import config
|
4
|
+
from janito.workspace import is_dir_empty
|
5
|
+
from janito.change.core import process_change_request
|
6
|
+
from ..base import BaseCLIHandler
|
7
|
+
|
8
|
+
class RequestHandler(BaseCLIHandler):
|
9
|
+
def handle(self, request: str, preview_only: bool = False):
|
10
|
+
"""Process a modification request"""
|
11
|
+
is_empty = is_dir_empty(config.workspace_dir)
|
12
|
+
if is_empty and not config.include:
|
13
|
+
self.console.print("\n[bold blue]Empty directory - will create new files as needed[/bold blue]")
|
14
|
+
|
15
|
+
success, history_file = process_change_request(request, preview_only)
|
16
|
+
|
17
|
+
if success and history_file and config.verbose:
|
18
|
+
try:
|
19
|
+
rel_path = history_file.relative_to(config.workspace_dir)
|
20
|
+
self.console.print(f"\nChanges saved to: ./{rel_path}")
|
21
|
+
except ValueError:
|
22
|
+
self.console.print(f"\nChanges saved to: {history_file}")
|
23
|
+
elif not success:
|
24
|
+
self.console.print("[red]Failed to process change request[/red]")
|
@@ -0,0 +1,9 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
from typing import List
|
3
|
+
from janito.workspace import preview_scan
|
4
|
+
from ..base import BaseCLIHandler
|
5
|
+
|
6
|
+
class ScanHandler(BaseCLIHandler):
|
7
|
+
def handle(self, paths_to_scan: List[Path]):
|
8
|
+
"""Preview files that would be analyzed"""
|
9
|
+
preview_scan(paths_to_scan)
|
janito/cli/history.py
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
from datetime import datetime, timezone
|
3
|
+
from typing import List
|
4
|
+
from rich.console import Console
|
5
|
+
from rich.table import Table
|
6
|
+
from janito.config import config
|
7
|
+
|
8
|
+
def get_history_path() -> Path:
|
9
|
+
"""Get the path to the history directory"""
|
10
|
+
history_dir = config.workspace_dir / '.janito' / 'history'
|
11
|
+
history_dir.mkdir(parents=True, exist_ok=True)
|
12
|
+
return history_dir
|
13
|
+
|
14
|
+
def save_to_history(request: str, response: str) -> None:
|
15
|
+
"""Save a request and its response to the history file"""
|
16
|
+
history_dir = get_history_path()
|
17
|
+
timestamp = datetime.now(timezone.utc).strftime('%Y-%m-%d_%H%M%S')
|
18
|
+
history_file = history_dir / f"{timestamp}_request.txt"
|
19
|
+
|
20
|
+
content = f"""Request: {request}
|
21
|
+
Timestamp: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')}
|
22
|
+
|
23
|
+
Response:
|
24
|
+
{response}
|
25
|
+
"""
|
26
|
+
history_file.write_text(content)
|
27
|
+
|
28
|
+
def display_history() -> None:
|
29
|
+
"""Display the history of requests"""
|
30
|
+
console = Console()
|
31
|
+
history_dir = get_history_path()
|
32
|
+
|
33
|
+
if not history_dir.exists():
|
34
|
+
console.print("[yellow]No history found[/yellow]")
|
35
|
+
return
|
36
|
+
|
37
|
+
table = Table(title="Request History")
|
38
|
+
table.add_column("Timestamp", style="cyan")
|
39
|
+
table.add_column("Request", style="white")
|
40
|
+
table.add_column("File", style="dim")
|
41
|
+
|
42
|
+
history_files = sorted(history_dir.glob("*_request.txt"), reverse=True)
|
43
|
+
|
44
|
+
if not history_files:
|
45
|
+
console.print("[yellow]No history found[/yellow]")
|
46
|
+
return
|
47
|
+
|
48
|
+
for history_file in history_files:
|
49
|
+
try:
|
50
|
+
content = history_file.read_text()
|
51
|
+
request_line = next(line for line in content.splitlines() if line.startswith("Request:"))
|
52
|
+
timestamp_line = next(line for line in content.splitlines() if line.startswith("Timestamp:"))
|
53
|
+
|
54
|
+
request = request_line.replace("Request:", "").strip()
|
55
|
+
timestamp = timestamp_line.replace("Timestamp:", "").strip()
|
56
|
+
|
57
|
+
table.add_row(timestamp, request, history_file.name)
|
58
|
+
except Exception as e:
|
59
|
+
console.print(f"[red]Error reading {history_file}: {e}[/red]")
|
60
|
+
|
61
|
+
console.print(table)
|
janito/cli/registry.py
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
from typing import Callable, Dict
|
2
|
+
from dataclasses import dataclass
|
3
|
+
|
4
|
+
@dataclass
|
5
|
+
class Command:
|
6
|
+
name: str
|
7
|
+
handler: Callable
|
8
|
+
help_text: str
|
9
|
+
|
10
|
+
class CommandRegistry:
|
11
|
+
def __init__(self):
|
12
|
+
self._commands: Dict[str, Command] = {}
|
13
|
+
|
14
|
+
def register(self, name: str, help_text: str):
|
15
|
+
def decorator(handler: Callable):
|
16
|
+
self._commands[name] = Command(name, handler, help_text)
|
17
|
+
return handler
|
18
|
+
return decorator
|
19
|
+
|
20
|
+
def get_command(self, name: str) -> Command:
|
21
|
+
return self._commands.get(name)
|
22
|
+
|
23
|
+
def get_all_commands(self) -> Dict[str, Command]:
|
24
|
+
return self._commands
|
25
|
+
|
26
|
+
registry = CommandRegistry()
|
janito/common.py
CHANGED
@@ -1,23 +1,54 @@
|
|
1
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn,
|
2
|
-
from
|
1
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
|
2
|
+
from rich.console import Console
|
3
|
+
from rich.rule import Rule
|
4
|
+
from janito.agents import agent
|
5
|
+
from .config import config
|
6
|
+
from rich import print
|
3
7
|
|
4
|
-
|
8
|
+
console = Console()
|
9
|
+
|
10
|
+
def progress_send_message(message: str) -> str:
|
5
11
|
"""
|
6
|
-
Send a message to
|
12
|
+
Send a message to the AI agent with a progress indicator and elapsed time.
|
7
13
|
|
8
14
|
Args:
|
9
|
-
claude: The Claude API agent instance
|
10
15
|
message: The message to send
|
11
16
|
|
12
17
|
Returns:
|
13
|
-
The response from
|
18
|
+
The response from the AI agent
|
14
19
|
"""
|
20
|
+
if config.debug:
|
21
|
+
console.print("[yellow]======= Sending message[/yellow]")
|
22
|
+
print(message)
|
23
|
+
console.print("[yellow]======= End of message[/yellow]")
|
24
|
+
|
15
25
|
with Progress(
|
16
26
|
SpinnerColumn(),
|
17
|
-
TextColumn("[progress.description]{task.description}"),
|
27
|
+
TextColumn("[progress.description]{task.description}", justify="center"),
|
18
28
|
TimeElapsedColumn(),
|
19
29
|
) as progress:
|
20
|
-
task = progress.add_task("Waiting for response from
|
21
|
-
response =
|
30
|
+
task = progress.add_task("Waiting for response from AI agent...", total=None)
|
31
|
+
response = agent.send_message(message)
|
22
32
|
progress.update(task, completed=True)
|
23
|
-
|
33
|
+
|
34
|
+
if config.debug:
|
35
|
+
console.print("[yellow]======= Received response[/yellow]")
|
36
|
+
print(response)
|
37
|
+
console.print("[yellow]======= End of response[/yellow]")
|
38
|
+
response_text = response.content[0].text
|
39
|
+
|
40
|
+
# Add token usage summary with detailed cache info
|
41
|
+
usage = response.usage
|
42
|
+
|
43
|
+
# Format cache info
|
44
|
+
cache_str = "(no cache used)"
|
45
|
+
if usage.cache_creation_input_tokens or usage.cache_read_input_tokens:
|
46
|
+
create_pct = (usage.cache_creation_input_tokens / usage.input_tokens) * 100
|
47
|
+
read_pct = (usage.cache_read_input_tokens / usage.input_tokens) * 100
|
48
|
+
cache_str = f"(cached in/out: {usage.cache_creation_input_tokens}[{create_pct:.1f}%]/{usage.cache_read_input_tokens}[{read_pct:.1f}%])"
|
49
|
+
|
50
|
+
percentage = (usage.output_tokens / usage.input_tokens) * 100
|
51
|
+
usage_text = f"Tokens: {usage.input_tokens} sent {cache_str}, {usage.output_tokens} received ({percentage:.1f}% ratio)"
|
52
|
+
console.print(Rule(usage_text, style="blue", align="center"))
|
53
|
+
|
54
|
+
return response_text
|
janito/config.py
CHANGED
@@ -1,30 +1,38 @@
|
|
1
|
-
from typing import Optional
|
1
|
+
from typing import Optional, List
|
2
2
|
import os
|
3
|
+
from pathlib import Path
|
3
4
|
|
4
5
|
class ConfigManager:
|
5
6
|
_instance = None
|
6
|
-
|
7
|
+
|
7
8
|
def __init__(self):
|
8
9
|
self.debug = False
|
9
10
|
self.verbose = False
|
10
11
|
self.debug_line = None
|
11
12
|
self.test_cmd = os.getenv('JANITO_TEST_CMD')
|
12
|
-
|
13
|
+
self.workspace_dir = Path.cwd()
|
14
|
+
self.raw = False
|
15
|
+
self.include: List[Path] = []
|
16
|
+
self.recursive: List[Path] = []
|
17
|
+
self.auto_apply: bool = False
|
18
|
+
self.tui: bool = False
|
19
|
+
self.skipwork: bool = False
|
20
|
+
|
13
21
|
@classmethod
|
14
22
|
def get_instance(cls) -> "ConfigManager":
|
15
23
|
if cls._instance is None:
|
16
24
|
cls._instance = cls()
|
17
25
|
return cls._instance
|
18
|
-
|
26
|
+
|
19
27
|
def set_debug(self, enabled: bool) -> None:
|
20
28
|
self.debug = enabled
|
21
29
|
|
22
30
|
def set_verbose(self, enabled: bool) -> None:
|
23
31
|
self.verbose = enabled
|
24
|
-
|
32
|
+
|
25
33
|
def set_debug_line(self, line: Optional[int]) -> None:
|
26
34
|
self.debug_line = line
|
27
|
-
|
35
|
+
|
28
36
|
def should_debug_line(self, line: int) -> bool:
|
29
37
|
"""Return True if we should show debug for this line number"""
|
30
38
|
return self.debug and (self.debug_line is None or self.debug_line == line)
|
@@ -33,5 +41,62 @@ class ConfigManager:
|
|
33
41
|
"""Set the test command, overriding environment variable"""
|
34
42
|
self.test_cmd = cmd if cmd is not None else os.getenv('JANITO_TEST_CMD')
|
35
43
|
|
44
|
+
def set_workspace_dir(self, path: Optional[Path]) -> None:
|
45
|
+
"""Set the workspace directory"""
|
46
|
+
self.workspace_dir = path if path is not None else Path.cwd()
|
47
|
+
|
48
|
+
def set_raw(self, enabled: bool) -> None:
|
49
|
+
"""Set raw output mode"""
|
50
|
+
self.raw = enabled
|
51
|
+
|
52
|
+
def set_include(self, paths: Optional[List[Path]]) -> None:
|
53
|
+
"""
|
54
|
+
Set additional paths to include.
|
55
|
+
|
56
|
+
Args:
|
57
|
+
paths: List of paths to include
|
58
|
+
|
59
|
+
Raises:
|
60
|
+
ValueError: If duplicate paths are provided
|
61
|
+
"""
|
62
|
+
if paths is None:
|
63
|
+
self.include = []
|
64
|
+
return
|
65
|
+
|
66
|
+
# Convert paths to absolute and resolve symlinks
|
67
|
+
resolved_paths = [p.absolute().resolve() for p in paths]
|
68
|
+
|
69
|
+
# Check for duplicates
|
70
|
+
seen_paths = set()
|
71
|
+
unique_paths = []
|
72
|
+
|
73
|
+
for path in resolved_paths:
|
74
|
+
if path in seen_paths:
|
75
|
+
raise ValueError(f"Duplicate path provided: {path}")
|
76
|
+
seen_paths.add(path)
|
77
|
+
unique_paths.append(path)
|
78
|
+
|
79
|
+
self.include = unique_paths
|
80
|
+
|
81
|
+
def set_auto_apply(self, enabled: bool) -> None:
|
82
|
+
"""Set auto apply mode"""
|
83
|
+
self.auto_apply = enabled
|
84
|
+
|
85
|
+
def set_tui(self, enabled: bool) -> None:
|
86
|
+
"""Set TUI mode"""
|
87
|
+
self.tui = enabled
|
88
|
+
|
89
|
+
def set_recursive(self, paths: Optional[List[Path]]) -> None:
|
90
|
+
"""Set paths to scan recursively
|
91
|
+
|
92
|
+
Args:
|
93
|
+
paths: List of directory paths to scan recursively, or None to disable recursive scanning
|
94
|
+
"""
|
95
|
+
self.recursive = paths
|
96
|
+
|
97
|
+
def set_skipwork(self, enabled: bool) -> None:
|
98
|
+
"""Set skipwork flag to skip scanning workspace_dir"""
|
99
|
+
self.skipwork = enabled
|
100
|
+
|
36
101
|
# Create a singleton instance
|
37
102
|
config = ConfigManager.get_instance()
|
janito/demo/__init__.py
ADDED
janito/demo/data.py
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
from typing import Dict, List
|
2
|
+
from .scenarios import DemoScenario
|
3
|
+
from .mock_data import get_mock_changes
|
4
|
+
|
5
|
+
def get_demo_scenarios() -> List[DemoScenario]:
|
6
|
+
"""Get list of predefined demo scenarios"""
|
7
|
+
return [
|
8
|
+
DemoScenario(
|
9
|
+
name="File Operations Demo",
|
10
|
+
description="Demonstrate various file operations with change viewer",
|
11
|
+
changes=get_mock_changes()
|
12
|
+
)
|
13
|
+
]
|
janito/demo/mock_data.py
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
from typing import List
|
2
|
+
from .operations import CreateOperation, ModifyOperation, RemoveOperation, MockOperation
|
3
|
+
|
4
|
+
def get_mock_changes() -> List[MockOperation]:
|
5
|
+
"""Get predefined mock changes for demo"""
|
6
|
+
return [
|
7
|
+
CreateOperation(
|
8
|
+
name="example/hello.py",
|
9
|
+
content="def greet():\n print('Hello, World!')\n"
|
10
|
+
),
|
11
|
+
ModifyOperation(
|
12
|
+
name="example/utils.py",
|
13
|
+
content="def process():\n return 'Processed'\n",
|
14
|
+
original_content="def old_process():\n return 'Old'\n"
|
15
|
+
),
|
16
|
+
RemoveOperation(
|
17
|
+
name="example/obsolete.py",
|
18
|
+
original_content="# Obsolete code\n"
|
19
|
+
)
|
20
|
+
]
|
@@ -0,0 +1,45 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
from typing import List, Optional
|
3
|
+
from enum import Enum, auto
|
4
|
+
from pathlib import Path
|
5
|
+
|
6
|
+
class MockOperationType(Enum):
|
7
|
+
CREATE = auto()
|
8
|
+
MODIFY = auto()
|
9
|
+
REMOVE = auto()
|
10
|
+
|
11
|
+
@dataclass
|
12
|
+
class MockOperation:
|
13
|
+
"""Base class for mock operations"""
|
14
|
+
operation_type: MockOperationType
|
15
|
+
name: str
|
16
|
+
reason: str
|
17
|
+
|
18
|
+
@dataclass
|
19
|
+
class CreateOperation(MockOperation):
|
20
|
+
"""Operation for creating new files"""
|
21
|
+
content: str
|
22
|
+
|
23
|
+
def __init__(self, name: str, content: str, reason: str = "Create new file"):
|
24
|
+
super().__init__(MockOperationType.CREATE, name, reason)
|
25
|
+
self.content = content
|
26
|
+
|
27
|
+
@dataclass
|
28
|
+
class ModifyOperation(MockOperation):
|
29
|
+
"""Operation for modifying existing files"""
|
30
|
+
content: str
|
31
|
+
original_content: str
|
32
|
+
|
33
|
+
def __init__(self, name: str, content: str, original_content: str, reason: str = "Modify existing file"):
|
34
|
+
super().__init__(MockOperationType.MODIFY, name, reason)
|
35
|
+
self.content = content
|
36
|
+
self.original_content = original_content
|
37
|
+
|
38
|
+
@dataclass
|
39
|
+
class RemoveOperation(MockOperation):
|
40
|
+
"""Operation for removing files"""
|
41
|
+
original_content: Optional[str] = None
|
42
|
+
|
43
|
+
def __init__(self, name: str, original_content: Optional[str] = None, reason: str = "Remove file"):
|
44
|
+
super().__init__(MockOperationType.REMOVE, name, reason)
|
45
|
+
self.original_content = original_content
|
janito/demo/runner.py
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
from typing import List, Optional
|
2
|
+
from pathlib import Path
|
3
|
+
from rich.console import Console
|
4
|
+
from rich.panel import Panel
|
5
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
6
|
+
from .scenarios import DemoScenario
|
7
|
+
from .operations import MockOperationType
|
8
|
+
from ..change.viewer import preview_all_changes
|
9
|
+
from ..change.parser import FileChange, ChangeOperation
|
10
|
+
|
11
|
+
class DemoRunner:
|
12
|
+
def __init__(self):
|
13
|
+
self.console = Console()
|
14
|
+
self.scenarios: List[DemoScenario] = []
|
15
|
+
|
16
|
+
def add_scenario(self, scenario: DemoScenario) -> None:
|
17
|
+
"""Add a demo scenario to the runner"""
|
18
|
+
self.scenarios.append(scenario)
|
19
|
+
|
20
|
+
def run_all(self) -> None:
|
21
|
+
"""Run all registered demo scenarios"""
|
22
|
+
with Progress(
|
23
|
+
SpinnerColumn(),
|
24
|
+
TextColumn("[progress.description]{task.description}"),
|
25
|
+
console=self.console
|
26
|
+
) as progress:
|
27
|
+
for scenario in self.scenarios:
|
28
|
+
task = progress.add_task(f"Running scenario: {scenario.name}")
|
29
|
+
self.preview_changes(scenario)
|
30
|
+
progress.update(task, completed=True)
|
31
|
+
|
32
|
+
def preview_changes(self, scenario: Optional[DemoScenario] = None) -> None:
|
33
|
+
"""Preview changes for a scenario using change viewer"""
|
34
|
+
if scenario is None:
|
35
|
+
if not self.scenarios:
|
36
|
+
self.console.print("[yellow]No scenarios to preview[/yellow]")
|
37
|
+
return
|
38
|
+
scenario = self.scenarios[0]
|
39
|
+
|
40
|
+
# Convert mock changes to FileChange objects
|
41
|
+
changes = []
|
42
|
+
for mock in scenario.changes:
|
43
|
+
# Map mock operation type to ChangeOperation
|
44
|
+
operation_map = {
|
45
|
+
MockOperationType.CREATE: ChangeOperation.CREATE_FILE,
|
46
|
+
MockOperationType.MODIFY: ChangeOperation.MODIFY_FILE,
|
47
|
+
MockOperationType.REMOVE: ChangeOperation.REMOVE_FILE
|
48
|
+
}
|
49
|
+
operation = operation_map[mock.operation_type]
|
50
|
+
change = FileChange(
|
51
|
+
operation=operation,
|
52
|
+
name=Path(mock.name),
|
53
|
+
content=mock.content if hasattr(mock, 'content') else None,
|
54
|
+
original_content=mock.original_content if hasattr(mock, 'original_content') else None
|
55
|
+
)
|
56
|
+
changes.append(change)
|
57
|
+
|
58
|
+
# Show changes using change viewer
|
59
|
+
preview_all_changes(self.console, changes)
|
janito/demo/scenarios.py
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
from typing import List, Dict, Optional
|
3
|
+
from rich.text import Text
|
4
|
+
from pathlib import Path
|
5
|
+
from .operations import MockOperation
|
6
|
+
from .mock_data import get_mock_changes
|
7
|
+
|
8
|
+
@dataclass
|
9
|
+
class DemoScenario:
|
10
|
+
name: str
|
11
|
+
description: str
|
12
|
+
changes: List[MockOperation]
|
13
|
+
|
14
|
+
def get_preview(self) -> Text:
|
15
|
+
"""Get a preview of the changes"""
|
16
|
+
text = Text()
|
17
|
+
text.append(f"Description: {self.description}\n\n", style="cyan")
|
18
|
+
|
19
|
+
# Group changes by operation
|
20
|
+
by_operation = {}
|
21
|
+
for change in self.changes:
|
22
|
+
if change.operation not in by_operation:
|
23
|
+
by_operation[change.operation] = []
|
24
|
+
by_operation[change.operation].append(change)
|
25
|
+
|
26
|
+
# Show changes grouped by operation
|
27
|
+
for operation_type, changes in by_operation.items():
|
28
|
+
text.append(f"\n{operation_type.name.title()} Operations:\n", style="yellow")
|
29
|
+
for change in changes:
|
30
|
+
text.append(f"• {change.name}\n", style="white")
|
31
|
+
|
32
|
+
return text
|