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.
Files changed (104) hide show
  1. janito/__init__.py +1 -1
  2. janito/__main__.py +102 -326
  3. janito/agents/__init__.py +16 -0
  4. janito/agents/agent.py +21 -0
  5. janito/{claude.py → agents/claudeai.py} +13 -17
  6. janito/agents/openai.py +53 -0
  7. janito/agents/test.py +34 -0
  8. janito/change/__init__.py +32 -0
  9. janito/change/__main__.py +0 -0
  10. janito/change/analysis/__init__.py +23 -0
  11. janito/change/analysis/__main__.py +7 -0
  12. janito/change/analysis/analyze.py +61 -0
  13. janito/change/analysis/formatting.py +78 -0
  14. janito/change/analysis/options.py +81 -0
  15. janito/change/analysis/prompts.py +98 -0
  16. janito/change/analysis/view/__init__.py +9 -0
  17. janito/change/analysis/view/terminal.py +171 -0
  18. janito/change/applier/__init__.py +5 -0
  19. janito/change/applier/file.py +58 -0
  20. janito/change/applier/main.py +156 -0
  21. janito/change/applier/text.py +245 -0
  22. janito/change/applier/workspace_dir.py +58 -0
  23. janito/change/core.py +131 -0
  24. janito/change/history.py +44 -0
  25. janito/change/operations.py +7 -0
  26. janito/change/parser.py +289 -0
  27. janito/change/play.py +54 -0
  28. janito/change/preview.py +82 -0
  29. janito/change/prompts.py +126 -0
  30. janito/change/test.py +0 -0
  31. janito/change/validator.py +251 -0
  32. janito/change/viewer/__init__.py +11 -0
  33. janito/change/viewer/content.py +66 -0
  34. janito/change/viewer/diff.py +43 -0
  35. janito/change/viewer/pager.py +56 -0
  36. janito/change/viewer/panels.py +555 -0
  37. janito/change/viewer/styling.py +103 -0
  38. janito/change/viewer/themes.py +55 -0
  39. janito/clear_statement_parser/clear_statement_format.txt +328 -0
  40. janito/clear_statement_parser/examples.txt +326 -0
  41. janito/clear_statement_parser/models.py +104 -0
  42. janito/clear_statement_parser/parser.py +496 -0
  43. janito/cli/__init__.py +2 -0
  44. janito/cli/base.py +30 -0
  45. janito/cli/commands.py +45 -0
  46. janito/cli/functions.py +111 -0
  47. janito/cli/handlers/ask.py +22 -0
  48. janito/cli/handlers/demo.py +22 -0
  49. janito/cli/handlers/request.py +24 -0
  50. janito/cli/handlers/scan.py +9 -0
  51. janito/cli/history.py +61 -0
  52. janito/cli/registry.py +26 -0
  53. janito/common.py +41 -10
  54. janito/config.py +71 -6
  55. janito/demo/__init__.py +4 -0
  56. janito/demo/data.py +13 -0
  57. janito/demo/mock_data.py +20 -0
  58. janito/demo/operations.py +45 -0
  59. janito/demo/runner.py +59 -0
  60. janito/demo/scenarios.py +32 -0
  61. janito/prompts.py +1 -65
  62. janito/qa.py +8 -5
  63. janito/review.py +13 -0
  64. janito/search_replace/README.md +146 -0
  65. janito/search_replace/__init__.py +6 -0
  66. janito/search_replace/__main__.py +21 -0
  67. janito/search_replace/core.py +119 -0
  68. janito/search_replace/parser.py +52 -0
  69. janito/search_replace/play.py +61 -0
  70. janito/search_replace/replacer.py +36 -0
  71. janito/search_replace/searcher.py +299 -0
  72. janito/shell/__init__.py +39 -0
  73. janito/shell/bus.py +31 -0
  74. janito/shell/commands.py +195 -0
  75. janito/shell/handlers.py +122 -0
  76. janito/shell/history.py +20 -0
  77. janito/shell/processor.py +52 -0
  78. janito/tui/__init__.py +21 -0
  79. janito/tui/base.py +22 -0
  80. janito/tui/flows/__init__.py +5 -0
  81. janito/tui/flows/changes.py +65 -0
  82. janito/tui/flows/content.py +128 -0
  83. janito/tui/flows/selection.py +117 -0
  84. janito/tui/screens/__init__.py +3 -0
  85. janito/tui/screens/app.py +1 -0
  86. janito/workspace/__init__.py +7 -0
  87. janito/workspace/analysis.py +121 -0
  88. janito/workspace/manager.py +48 -0
  89. janito/workspace/scan.py +232 -0
  90. janito-0.6.0.dist-info/METADATA +185 -0
  91. janito-0.6.0.dist-info/RECORD +95 -0
  92. {janito-0.4.0.dist-info → janito-0.6.0.dist-info}/WHEEL +1 -1
  93. janito/analysis.py +0 -281
  94. janito/changeapplier.py +0 -436
  95. janito/changeviewer.py +0 -350
  96. janito/console.py +0 -330
  97. janito/contentchange.py +0 -84
  98. janito/contextparser.py +0 -113
  99. janito/fileparser.py +0 -125
  100. janito/scan.py +0 -137
  101. janito-0.4.0.dist-info/METADATA +0 -164
  102. janito-0.4.0.dist-info/RECORD +0 -21
  103. {janito-0.4.0.dist-info → janito-0.6.0.dist-info}/entry_points.txt +0 -0
  104. {janito-0.4.0.dist-info → janito-0.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -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, BarColumn, TimeElapsedColumn
2
- from janito.claude import ClaudeAPIAgent
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
- def progress_send_message(claude: ClaudeAPIAgent, message: str) -> str:
8
+ console = Console()
9
+
10
+ def progress_send_message(message: str) -> str:
5
11
  """
6
- Send a message to Claude with a progress indicator and elapsed time.
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 Claude
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 Claude...", total=None)
21
- response = claude.send_message(message)
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
- return response
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()
@@ -0,0 +1,4 @@
1
+ from .runner import DemoRunner
2
+ from .scenarios import DemoScenario
3
+
4
+ __all__ = ['DemoRunner', 'DemoScenario']
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
+ ]
@@ -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)
@@ -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