groknroll 2.0.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.
- groknroll/__init__.py +36 -0
- groknroll/__main__.py +9 -0
- groknroll/agents/__init__.py +18 -0
- groknroll/agents/agent_manager.py +187 -0
- groknroll/agents/base_agent.py +118 -0
- groknroll/agents/build_agent.py +231 -0
- groknroll/agents/plan_agent.py +215 -0
- groknroll/cli/__init__.py +7 -0
- groknroll/cli/enhanced_cli.py +372 -0
- groknroll/cli/large_codebase_cli.py +413 -0
- groknroll/cli/main.py +331 -0
- groknroll/cli/rlm_commands.py +258 -0
- groknroll/clients/__init__.py +63 -0
- groknroll/clients/anthropic.py +112 -0
- groknroll/clients/azure_openai.py +142 -0
- groknroll/clients/base_lm.py +33 -0
- groknroll/clients/gemini.py +162 -0
- groknroll/clients/litellm.py +105 -0
- groknroll/clients/openai.py +129 -0
- groknroll/clients/portkey.py +94 -0
- groknroll/core/__init__.py +9 -0
- groknroll/core/agent.py +339 -0
- groknroll/core/comms_utils.py +264 -0
- groknroll/core/context.py +251 -0
- groknroll/core/exceptions.py +181 -0
- groknroll/core/large_codebase.py +564 -0
- groknroll/core/lm_handler.py +206 -0
- groknroll/core/rlm.py +446 -0
- groknroll/core/rlm_codebase.py +448 -0
- groknroll/core/rlm_integration.py +256 -0
- groknroll/core/types.py +276 -0
- groknroll/environments/__init__.py +34 -0
- groknroll/environments/base_env.py +182 -0
- groknroll/environments/constants.py +32 -0
- groknroll/environments/docker_repl.py +336 -0
- groknroll/environments/local_repl.py +388 -0
- groknroll/environments/modal_repl.py +502 -0
- groknroll/environments/prime_repl.py +588 -0
- groknroll/logger/__init__.py +4 -0
- groknroll/logger/rlm_logger.py +63 -0
- groknroll/logger/verbose.py +393 -0
- groknroll/operations/__init__.py +15 -0
- groknroll/operations/bash_ops.py +447 -0
- groknroll/operations/file_ops.py +473 -0
- groknroll/operations/git_ops.py +620 -0
- groknroll/oracle/__init__.py +11 -0
- groknroll/oracle/codebase_indexer.py +238 -0
- groknroll/oracle/oracle_agent.py +278 -0
- groknroll/setup.py +34 -0
- groknroll/storage/__init__.py +14 -0
- groknroll/storage/database.py +272 -0
- groknroll/storage/models.py +128 -0
- groknroll/utils/__init__.py +0 -0
- groknroll/utils/parsing.py +168 -0
- groknroll/utils/prompts.py +146 -0
- groknroll/utils/rlm_utils.py +19 -0
- groknroll-2.0.0.dist-info/METADATA +246 -0
- groknroll-2.0.0.dist-info/RECORD +62 -0
- groknroll-2.0.0.dist-info/WHEEL +5 -0
- groknroll-2.0.0.dist-info/entry_points.txt +3 -0
- groknroll-2.0.0.dist-info/licenses/LICENSE +21 -0
- groknroll-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI commands for working with large codebases
|
|
3
|
+
|
|
4
|
+
Specialized commands to handle massive projects without context rot.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
from rich.console import Console
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
from rich.tree import Tree
|
|
16
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
17
|
+
|
|
18
|
+
from groknroll.core.large_codebase import LargeCodebaseHandler
|
|
19
|
+
from groknroll.storage.database import Database
|
|
20
|
+
from groknroll.agents.agent_manager import AgentManager
|
|
21
|
+
|
|
22
|
+
console = Console()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@click.group()
|
|
26
|
+
def large():
|
|
27
|
+
"""Commands for working with large codebases"""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@large.command()
|
|
32
|
+
@click.option('--path', type=click.Path(exists=True), help='Project path')
|
|
33
|
+
def map_codebase(path: Optional[str]):
|
|
34
|
+
"""
|
|
35
|
+
Create hierarchical map of large codebase
|
|
36
|
+
|
|
37
|
+
This command chunks your codebase intelligently and creates
|
|
38
|
+
a dependency graph for efficient navigation.
|
|
39
|
+
"""
|
|
40
|
+
project_path = Path(path) if path else Path.cwd()
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
with Progress(
|
|
44
|
+
SpinnerColumn(),
|
|
45
|
+
TextColumn("[progress.description]{task.description}"),
|
|
46
|
+
console=console
|
|
47
|
+
) as progress:
|
|
48
|
+
task = progress.add_task("Mapping codebase...", total=None)
|
|
49
|
+
|
|
50
|
+
db = Database()
|
|
51
|
+
handler = LargeCodebaseHandler(project_path, db)
|
|
52
|
+
codebase_map = handler.chunk_codebase()
|
|
53
|
+
|
|
54
|
+
progress.update(task, completed=True)
|
|
55
|
+
|
|
56
|
+
# Display results
|
|
57
|
+
console.print()
|
|
58
|
+
console.print(Panel.fit(
|
|
59
|
+
f"[bold green]✓ Codebase mapped successfully![/bold green]\n\n"
|
|
60
|
+
f"[cyan]Total Files:[/cyan] {codebase_map.total_files:,}\n"
|
|
61
|
+
f"[cyan]Total Lines:[/cyan] {codebase_map.total_lines:,}\n"
|
|
62
|
+
f"[cyan]Total Chunks:[/cyan] {len(codebase_map.chunks):,}\n"
|
|
63
|
+
f"[cyan]Modules:[/cyan] {len(codebase_map.modules):,}",
|
|
64
|
+
title="[bold]Codebase Map[/bold]",
|
|
65
|
+
border_style="green"
|
|
66
|
+
))
|
|
67
|
+
|
|
68
|
+
# Language breakdown
|
|
69
|
+
overview = handler.get_codebase_overview()
|
|
70
|
+
|
|
71
|
+
if overview['languages']:
|
|
72
|
+
console.print()
|
|
73
|
+
lang_table = Table(title="Language Distribution", show_header=True)
|
|
74
|
+
lang_table.add_column("Language", style="cyan")
|
|
75
|
+
lang_table.add_column("Files", justify="right", style="green")
|
|
76
|
+
lang_table.add_column("Lines", justify="right", style="blue")
|
|
77
|
+
lang_table.add_column("% of Total", justify="right", style="magenta")
|
|
78
|
+
|
|
79
|
+
total_lines = overview['total_lines']
|
|
80
|
+
for lang, stats in sorted(
|
|
81
|
+
overview['languages'].items(),
|
|
82
|
+
key=lambda x: x[1]['lines'],
|
|
83
|
+
reverse=True
|
|
84
|
+
):
|
|
85
|
+
percentage = (stats['lines'] / total_lines * 100) if total_lines > 0 else 0
|
|
86
|
+
lang_table.add_row(
|
|
87
|
+
lang,
|
|
88
|
+
f"{stats['files']:,}",
|
|
89
|
+
f"{stats['lines']:,}",
|
|
90
|
+
f"{percentage:.1f}%"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
console.print(lang_table)
|
|
94
|
+
|
|
95
|
+
# Top modules
|
|
96
|
+
if overview['top_modules']:
|
|
97
|
+
console.print()
|
|
98
|
+
mod_table = Table(title="Largest Modules", show_header=True)
|
|
99
|
+
mod_table.add_column("Module", style="cyan")
|
|
100
|
+
mod_table.add_column("Lines", justify="right", style="blue")
|
|
101
|
+
|
|
102
|
+
for mod in overview['top_modules'][:10]:
|
|
103
|
+
mod_table.add_row(mod['module'], f"{mod['lines']:,}")
|
|
104
|
+
|
|
105
|
+
console.print(mod_table)
|
|
106
|
+
|
|
107
|
+
console.print(f"\n[dim]Map saved to: {project_path}/.groknroll/cache/codebase_map.json[/dim]")
|
|
108
|
+
|
|
109
|
+
except Exception as e:
|
|
110
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
111
|
+
import traceback
|
|
112
|
+
traceback.print_exc()
|
|
113
|
+
sys.exit(1)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@large.command()
|
|
117
|
+
@click.option('--path', type=click.Path(exists=True), help='Project path')
|
|
118
|
+
def overview(path: Optional[str]):
|
|
119
|
+
"""Show high-level overview of large codebase"""
|
|
120
|
+
project_path = Path(path) if path else Path.cwd()
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
db = Database()
|
|
124
|
+
handler = LargeCodebaseHandler(project_path, db)
|
|
125
|
+
overview = handler.get_codebase_overview()
|
|
126
|
+
|
|
127
|
+
# Display overview
|
|
128
|
+
console.print()
|
|
129
|
+
console.print(Panel.fit(
|
|
130
|
+
f"[bold cyan]Project:[/bold cyan] {project_path.name}\n"
|
|
131
|
+
f"[cyan]Files:[/cyan] {overview['total_files']:,}\n"
|
|
132
|
+
f"[cyan]Lines of Code:[/cyan] {overview['total_lines']:,}\n"
|
|
133
|
+
f"[cyan]Modules:[/cyan] {overview['total_modules']:,}\n"
|
|
134
|
+
f"[cyan]Chunks:[/cyan] {overview['total_chunks']:,}",
|
|
135
|
+
title="[bold]Codebase Overview[/bold]",
|
|
136
|
+
border_style="cyan"
|
|
137
|
+
))
|
|
138
|
+
|
|
139
|
+
# Languages
|
|
140
|
+
console.print()
|
|
141
|
+
lang_table = Table(title="Languages", show_header=True)
|
|
142
|
+
lang_table.add_column("Language", style="cyan")
|
|
143
|
+
lang_table.add_column("Files", justify="right", style="green")
|
|
144
|
+
lang_table.add_column("Lines", justify="right", style="blue")
|
|
145
|
+
|
|
146
|
+
for lang, stats in sorted(
|
|
147
|
+
overview['languages'].items(),
|
|
148
|
+
key=lambda x: x[1]['lines'],
|
|
149
|
+
reverse=True
|
|
150
|
+
):
|
|
151
|
+
lang_table.add_row(
|
|
152
|
+
lang,
|
|
153
|
+
f"{stats['files']:,}",
|
|
154
|
+
f"{stats['lines']:,}"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
console.print(lang_table)
|
|
158
|
+
|
|
159
|
+
except Exception as e:
|
|
160
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
161
|
+
sys.exit(1)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@large.command()
|
|
165
|
+
@click.argument('module_path')
|
|
166
|
+
@click.option('--path', type=click.Path(exists=True), help='Project path')
|
|
167
|
+
def inspect_module(module_path: str, path: Optional[str]):
|
|
168
|
+
"""Inspect a specific module (directory) in detail"""
|
|
169
|
+
project_path = Path(path) if path else Path.cwd()
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
db = Database()
|
|
173
|
+
handler = LargeCodebaseHandler(project_path, db)
|
|
174
|
+
summary = handler.get_module_summary(module_path)
|
|
175
|
+
|
|
176
|
+
if 'error' in summary:
|
|
177
|
+
console.print(f"[bold red]Error:[/bold red] {summary['error']}")
|
|
178
|
+
|
|
179
|
+
if 'available_modules' in summary:
|
|
180
|
+
console.print("\n[yellow]Available modules:[/yellow]")
|
|
181
|
+
for mod in summary['available_modules']:
|
|
182
|
+
console.print(f" • {mod}")
|
|
183
|
+
|
|
184
|
+
sys.exit(1)
|
|
185
|
+
|
|
186
|
+
# Display module summary
|
|
187
|
+
console.print()
|
|
188
|
+
console.print(Panel.fit(
|
|
189
|
+
f"[bold cyan]Module:[/bold cyan] {summary['module']}\n"
|
|
190
|
+
f"[cyan]Files:[/cyan] {summary['files']}\n"
|
|
191
|
+
f"[cyan]Total Lines:[/cyan] {summary['total_lines']:,}",
|
|
192
|
+
title="[bold]Module Summary[/bold]",
|
|
193
|
+
border_style="cyan"
|
|
194
|
+
))
|
|
195
|
+
|
|
196
|
+
# Languages in module
|
|
197
|
+
if summary['languages']:
|
|
198
|
+
console.print()
|
|
199
|
+
lang_table = Table(title="Languages in Module", show_header=True)
|
|
200
|
+
lang_table.add_column("Language", style="cyan")
|
|
201
|
+
lang_table.add_column("Files", justify="right", style="green")
|
|
202
|
+
lang_table.add_column("Lines", justify="right", style="blue")
|
|
203
|
+
|
|
204
|
+
for lang, stats in sorted(
|
|
205
|
+
summary['languages'].items(),
|
|
206
|
+
key=lambda x: x[1]['lines'],
|
|
207
|
+
reverse=True
|
|
208
|
+
):
|
|
209
|
+
lang_table.add_row(
|
|
210
|
+
lang,
|
|
211
|
+
str(stats['files']),
|
|
212
|
+
f"{stats['lines']:,}"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
console.print(lang_table)
|
|
216
|
+
|
|
217
|
+
# Files in module
|
|
218
|
+
if summary['chunks']:
|
|
219
|
+
console.print()
|
|
220
|
+
file_table = Table(title="Files", show_header=True)
|
|
221
|
+
file_table.add_column("File", style="cyan")
|
|
222
|
+
file_table.add_column("Language", style="green")
|
|
223
|
+
file_table.add_column("Lines", justify="right", style="blue")
|
|
224
|
+
|
|
225
|
+
for chunk in sorted(summary['chunks'], key=lambda x: x['lines'], reverse=True)[:20]:
|
|
226
|
+
file_table.add_row(
|
|
227
|
+
chunk['file'],
|
|
228
|
+
chunk['language'],
|
|
229
|
+
f"{chunk['lines']:,}"
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
console.print(file_table)
|
|
233
|
+
|
|
234
|
+
if len(summary['chunks']) > 20:
|
|
235
|
+
console.print(f"\n[dim]Showing 20 of {len(summary['chunks'])} files[/dim]")
|
|
236
|
+
|
|
237
|
+
except Exception as e:
|
|
238
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
239
|
+
sys.exit(1)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@large.command()
|
|
243
|
+
@click.argument('symbol')
|
|
244
|
+
@click.option('--path', type=click.Path(exists=True), help='Project path')
|
|
245
|
+
def find_symbol(symbol: str, path: Optional[str]):
|
|
246
|
+
"""Find definition of a symbol across entire codebase"""
|
|
247
|
+
project_path = Path(path) if path else Path.cwd()
|
|
248
|
+
|
|
249
|
+
try:
|
|
250
|
+
with console.status(f"[bold cyan]Searching for '{symbol}'..."):
|
|
251
|
+
db = Database()
|
|
252
|
+
handler = LargeCodebaseHandler(project_path, db)
|
|
253
|
+
results = handler.navigate_to_definition(symbol)
|
|
254
|
+
|
|
255
|
+
if not results:
|
|
256
|
+
console.print(f"[yellow]No definitions found for '{symbol}'[/yellow]")
|
|
257
|
+
return
|
|
258
|
+
|
|
259
|
+
# Display results
|
|
260
|
+
console.print()
|
|
261
|
+
table = Table(title=f"Definitions of '{symbol}'", show_header=True)
|
|
262
|
+
table.add_column("File", style="cyan")
|
|
263
|
+
table.add_column("Line", justify="right", style="green")
|
|
264
|
+
table.add_column("Code", style="white")
|
|
265
|
+
|
|
266
|
+
for result in results[:20]:
|
|
267
|
+
table.add_row(
|
|
268
|
+
result['file'],
|
|
269
|
+
str(result['line']),
|
|
270
|
+
result['snippet'][:80]
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
console.print(table)
|
|
274
|
+
|
|
275
|
+
if len(results) > 20:
|
|
276
|
+
console.print(f"\n[dim]Showing 20 of {len(results)} results[/dim]")
|
|
277
|
+
|
|
278
|
+
except Exception as e:
|
|
279
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
280
|
+
sys.exit(1)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
@large.command()
|
|
284
|
+
@click.option('--path', type=click.Path(exists=True), help='Project path')
|
|
285
|
+
def check_changes(path: Optional[str]):
|
|
286
|
+
"""Check which files have changed since last analysis (incremental)"""
|
|
287
|
+
project_path = Path(path) if path else Path.cwd()
|
|
288
|
+
|
|
289
|
+
try:
|
|
290
|
+
with console.status("[bold cyan]Checking for changes..."):
|
|
291
|
+
db = Database()
|
|
292
|
+
handler = LargeCodebaseHandler(project_path, db)
|
|
293
|
+
changed = handler.get_changed_chunks()
|
|
294
|
+
|
|
295
|
+
if not changed:
|
|
296
|
+
console.print("[bold green]✓ No changes detected[/bold green]")
|
|
297
|
+
console.print("\n[dim]All files are up to date with last analysis[/dim]")
|
|
298
|
+
return
|
|
299
|
+
|
|
300
|
+
# Display changed files
|
|
301
|
+
console.print()
|
|
302
|
+
console.print(f"[bold yellow]⚠ {len(changed)} file(s) changed[/bold yellow]\n")
|
|
303
|
+
|
|
304
|
+
table = Table(title="Changed Files", show_header=True)
|
|
305
|
+
table.add_column("File", style="cyan")
|
|
306
|
+
table.add_column("Language", style="green")
|
|
307
|
+
table.add_column("Lines", justify="right", style="blue")
|
|
308
|
+
|
|
309
|
+
for chunk in changed[:50]:
|
|
310
|
+
relative_path = chunk.path.relative_to(project_path)
|
|
311
|
+
table.add_row(
|
|
312
|
+
str(relative_path),
|
|
313
|
+
chunk.language,
|
|
314
|
+
f"{chunk.size:,}"
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
console.print(table)
|
|
318
|
+
|
|
319
|
+
if len(changed) > 50:
|
|
320
|
+
console.print(f"\n[dim]Showing 50 of {len(changed)} changed files[/dim]")
|
|
321
|
+
|
|
322
|
+
console.print(f"\n[dim]Tip: Use 'groknroll large analyze-changes' to analyze these changes with RLM[/dim]")
|
|
323
|
+
|
|
324
|
+
except Exception as e:
|
|
325
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
326
|
+
sys.exit(1)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
@large.command()
|
|
330
|
+
@click.argument('task')
|
|
331
|
+
@click.option('--path', type=click.Path(exists=True), help='Project path')
|
|
332
|
+
@click.option('--max-context', type=int, default=50, help='Max chunks to include in context')
|
|
333
|
+
def smart_task(task: str, path: Optional[str], max_context: int):
|
|
334
|
+
"""
|
|
335
|
+
Execute task with smart context selection (no context rot!)
|
|
336
|
+
|
|
337
|
+
Uses intelligent chunking to only load relevant parts of codebase.
|
|
338
|
+
Perfect for massive projects with 100K+ lines.
|
|
339
|
+
"""
|
|
340
|
+
project_path = Path(path) if path else Path.cwd()
|
|
341
|
+
|
|
342
|
+
try:
|
|
343
|
+
# Get relevant context
|
|
344
|
+
with console.status("[bold cyan]Finding relevant code..."):
|
|
345
|
+
db = Database()
|
|
346
|
+
handler = LargeCodebaseHandler(project_path, db)
|
|
347
|
+
relevant_chunks = handler.get_relevant_context(task, max_chunks=max_context)
|
|
348
|
+
|
|
349
|
+
console.print()
|
|
350
|
+
console.print(Panel(
|
|
351
|
+
f"[cyan]Found {len(relevant_chunks)} relevant code chunks[/cyan]\n"
|
|
352
|
+
f"[dim]Total context: ~{sum(c.size for c in relevant_chunks):,} lines[/dim]",
|
|
353
|
+
title="[bold]Smart Context Selection[/bold]",
|
|
354
|
+
border_style="cyan"
|
|
355
|
+
))
|
|
356
|
+
|
|
357
|
+
# Show what's being analyzed
|
|
358
|
+
console.print("\n[bold]Analyzing:[/bold]")
|
|
359
|
+
for chunk in relevant_chunks[:10]:
|
|
360
|
+
relative_path = chunk.path.relative_to(project_path)
|
|
361
|
+
console.print(f" • {relative_path} ({chunk.size} lines)")
|
|
362
|
+
|
|
363
|
+
if len(relevant_chunks) > 10:
|
|
364
|
+
console.print(f" • ... and {len(relevant_chunks) - 10} more files")
|
|
365
|
+
|
|
366
|
+
# Execute with build agent
|
|
367
|
+
console.print()
|
|
368
|
+
with console.status("[bold cyan]Build agent working..."):
|
|
369
|
+
manager = AgentManager(project_path)
|
|
370
|
+
|
|
371
|
+
# Add relevant files to context
|
|
372
|
+
context = {
|
|
373
|
+
"relevant_files": [
|
|
374
|
+
{
|
|
375
|
+
"path": str(c.path.relative_to(project_path)),
|
|
376
|
+
"lines": c.size,
|
|
377
|
+
"language": c.language
|
|
378
|
+
}
|
|
379
|
+
for c in relevant_chunks
|
|
380
|
+
],
|
|
381
|
+
"total_context_lines": sum(c.size for c in relevant_chunks)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
response = manager.execute(task, agent="build", context=context)
|
|
385
|
+
|
|
386
|
+
# Display response
|
|
387
|
+
console.print()
|
|
388
|
+
if response.success:
|
|
389
|
+
from rich.markdown import Markdown
|
|
390
|
+
console.print(Panel(
|
|
391
|
+
Markdown(response.message),
|
|
392
|
+
title="[bold green]✓ Build Agent[/bold green]",
|
|
393
|
+
border_style="green"
|
|
394
|
+
))
|
|
395
|
+
|
|
396
|
+
console.print(f"\n[dim]Completed in {response.time:.1f}s "
|
|
397
|
+
f"(cost: ${response.cost:.4f})[/dim]")
|
|
398
|
+
else:
|
|
399
|
+
console.print(Panel(
|
|
400
|
+
response.message,
|
|
401
|
+
title="[bold red]✗ Build Agent Failed[/bold red]",
|
|
402
|
+
border_style="red"
|
|
403
|
+
))
|
|
404
|
+
|
|
405
|
+
except Exception as e:
|
|
406
|
+
console.print(f"[bold red]Error:[/bold red] {e}")
|
|
407
|
+
import traceback
|
|
408
|
+
traceback.print_exc()
|
|
409
|
+
sys.exit(1)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
if __name__ == "__main__":
|
|
413
|
+
large()
|