rbx.cp 0.5.24__py3-none-any.whl → 0.5.26__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.
- rbx/box/code.py +1 -1
- rbx/box/contest/main.py +9 -6
- rbx/box/main.py +75 -34
- rbx/box/presets/__init__.py +37 -10
- rbx/box/solutions.py +55 -9
- rbx/box/validators.py +15 -3
- rbx/console.py +12 -0
- rbx/grading/steps.py +11 -4
- {rbx_cp-0.5.24.dist-info → rbx_cp-0.5.26.dist-info}/METADATA +1 -1
- {rbx_cp-0.5.24.dist-info → rbx_cp-0.5.26.dist-info}/RECORD +13 -13
- {rbx_cp-0.5.24.dist-info → rbx_cp-0.5.26.dist-info}/LICENSE +0 -0
- {rbx_cp-0.5.24.dist-info → rbx_cp-0.5.26.dist-info}/WHEEL +0 -0
- {rbx_cp-0.5.24.dist-info → rbx_cp-0.5.26.dist-info}/entry_points.txt +0 -0
rbx/box/code.py
CHANGED
@@ -75,7 +75,7 @@ def is_executable_sanitized(executable: DigestOrSource) -> bool:
|
|
75
75
|
|
76
76
|
def add_sanitizer_flags_to_command(command: str) -> str:
|
77
77
|
if is_cxx_command(command):
|
78
|
-
return command + ' -fsanitize=address,undefined -fno-omit-frame-pointer'
|
78
|
+
return command + ' -fsanitize=address,undefined -fno-omit-frame-pointer -g'
|
79
79
|
return command
|
80
80
|
|
81
81
|
|
rbx/box/contest/main.py
CHANGED
@@ -147,12 +147,15 @@ def add(path: str, short_name: str, preset: Optional[str] = None):
|
|
147
147
|
|
148
148
|
ru, contest = contest_package.get_ruyaml()
|
149
149
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
150
|
+
item = {
|
151
|
+
'short_name': short_name,
|
152
|
+
'path': path,
|
153
|
+
}
|
154
|
+
if 'problems' not in contest:
|
155
|
+
contest['problems'] = [item]
|
156
|
+
else:
|
157
|
+
contest['problems'].append(item)
|
158
|
+
|
156
159
|
dest = find_contest_yaml()
|
157
160
|
assert dest is not None
|
158
161
|
utils.save_ruyaml(dest, ru, contest)
|
rbx/box/main.py
CHANGED
@@ -42,6 +42,7 @@ from rbx.box.solutions import (
|
|
42
42
|
estimate_time_limit,
|
43
43
|
get_exact_matching_solutions,
|
44
44
|
get_matching_solutions,
|
45
|
+
pick_solutions,
|
45
46
|
print_run_report,
|
46
47
|
run_and_print_interactive_solutions,
|
47
48
|
run_solutions,
|
@@ -143,6 +144,13 @@ def run(
|
|
143
144
|
'-s',
|
144
145
|
help='Whether to compile the solutions with sanitizers enabled.',
|
145
146
|
),
|
147
|
+
choice: bool = typer.Option(
|
148
|
+
False,
|
149
|
+
'--choice',
|
150
|
+
'--choose',
|
151
|
+
'-c',
|
152
|
+
help='Whether to pick solutions interactively.',
|
153
|
+
),
|
146
154
|
):
|
147
155
|
main_solution = package.get_main_solution()
|
148
156
|
if check and main_solution is None:
|
@@ -174,31 +182,37 @@ def run(
|
|
174
182
|
if override_tl is None:
|
175
183
|
raise typer.Exit(1)
|
176
184
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
185
|
+
if sanitized:
|
186
|
+
console.console.print(
|
187
|
+
'[warning]Sanitizers are running, so the time limit for the problem will be dropped, '
|
188
|
+
'and the environment default time limit will be used instead.[/warning]'
|
189
|
+
)
|
190
|
+
|
191
|
+
tracked_solutions = None
|
192
|
+
if outcome is not None:
|
193
|
+
tracked_solutions = {
|
194
|
+
str(solution.path)
|
195
|
+
for solution in get_matching_solutions(ExpectedOutcome(outcome))
|
196
|
+
}
|
197
|
+
if solution:
|
198
|
+
tracked_solutions = {solution}
|
183
199
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
}
|
190
|
-
if solution:
|
191
|
-
tracked_solutions = {solution}
|
200
|
+
if choice:
|
201
|
+
tracked_solutions = set(pick_solutions(tracked_solutions))
|
202
|
+
if not tracked_solutions:
|
203
|
+
console.console.print('[error]No solutions selected. Exiting.[/error]')
|
204
|
+
raise typer.Exit(1)
|
192
205
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
206
|
+
if sanitized and tracked_solutions is None:
|
207
|
+
console.console.print(
|
208
|
+
'[warning]Sanitizers are running, and no solutions were specified to run. Will only run [item]ACCEPTED[/item] solutions.'
|
209
|
+
)
|
210
|
+
tracked_solutions = {
|
211
|
+
str(solution.path)
|
212
|
+
for solution in get_exact_matching_solutions(ExpectedOutcome.ACCEPTED)
|
213
|
+
}
|
201
214
|
|
215
|
+
with utils.StatusProgress('Running solutions...') as s:
|
202
216
|
solution_result = run_solutions(
|
203
217
|
progress=s,
|
204
218
|
tracked_solutions=tracked_solutions,
|
@@ -338,6 +352,13 @@ def irun(
|
|
338
352
|
'-s',
|
339
353
|
help='Whether to compile the solutions with sanitizers enabled.',
|
340
354
|
),
|
355
|
+
choice: bool = typer.Option(
|
356
|
+
False,
|
357
|
+
'--choice',
|
358
|
+
'--choose',
|
359
|
+
'-c',
|
360
|
+
help='Whether to pick solutions interactively.',
|
361
|
+
),
|
341
362
|
):
|
342
363
|
if not print:
|
343
364
|
console.console.print(
|
@@ -364,6 +385,13 @@ def irun(
|
|
364
385
|
}
|
365
386
|
if solution:
|
366
387
|
tracked_solutions = {solution}
|
388
|
+
|
389
|
+
if choice:
|
390
|
+
tracked_solutions = set(pick_solutions(tracked_solutions))
|
391
|
+
if not tracked_solutions:
|
392
|
+
console.console.print('[error]No solutions selected. Exiting.[/error]')
|
393
|
+
raise typer.Exit(1)
|
394
|
+
|
367
395
|
if sanitized and tracked_solutions is None:
|
368
396
|
console.console.print(
|
369
397
|
'[warning]Sanitizers are running, and no solutions were specified to run. Will only run [item]ACCEPTED[/item] solutions.'
|
@@ -373,18 +401,20 @@ def irun(
|
|
373
401
|
for solution in get_exact_matching_solutions(ExpectedOutcome.ACCEPTED)
|
374
402
|
}
|
375
403
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
404
|
+
with utils.StatusProgress('Running solutions...') as s:
|
405
|
+
asyncio.run(
|
406
|
+
run_and_print_interactive_solutions(
|
407
|
+
progress=s,
|
408
|
+
tracked_solutions=tracked_solutions,
|
409
|
+
check=check,
|
410
|
+
verification=VerificationLevel(verification),
|
411
|
+
generator=generators.get_call_from_string(generator)
|
412
|
+
if generator is not None
|
413
|
+
else None,
|
414
|
+
print=print,
|
415
|
+
sanitized=sanitized,
|
416
|
+
)
|
386
417
|
)
|
387
|
-
)
|
388
418
|
|
389
419
|
|
390
420
|
@app.command('create, c', help='Create a new problem package.')
|
@@ -510,6 +540,8 @@ def stress(
|
|
510
540
|
name=testgroup, generatorScript=CodeItem(path=new_script_path)
|
511
541
|
)
|
512
542
|
ru, problem_yml = package.get_ruyaml()
|
543
|
+
if 'testcases' not in problem_yml:
|
544
|
+
problem_yml['testcases'] = []
|
513
545
|
problem_yml['testcases'].append(
|
514
546
|
{
|
515
547
|
'name': testgroup,
|
@@ -551,7 +583,10 @@ def stress(
|
|
551
583
|
@app.command('compile', help='Compile an asset given its path.')
|
552
584
|
@package.within_problem
|
553
585
|
def compile_command(
|
554
|
-
path: Annotated[
|
586
|
+
path: Annotated[
|
587
|
+
Optional[str],
|
588
|
+
typer.Argument(help='Path to the asset to compile.'),
|
589
|
+
] = None,
|
555
590
|
sanitized: bool = typer.Option(
|
556
591
|
False,
|
557
592
|
'--sanitized',
|
@@ -565,6 +600,12 @@ def compile_command(
|
|
565
600
|
help='Whether to compile the asset with warnings enabled.',
|
566
601
|
),
|
567
602
|
):
|
603
|
+
if path is None:
|
604
|
+
path = questionary.path("What's the path to your asset?").ask()
|
605
|
+
if path is None:
|
606
|
+
console.console.print('[error]No path specified.[/error]')
|
607
|
+
raise typer.Exit(1)
|
608
|
+
|
568
609
|
compile.any(path, sanitized, warnings)
|
569
610
|
|
570
611
|
|
rbx/box/presets/__init__.py
CHANGED
@@ -68,13 +68,14 @@ def _find_nested_preset(root: pathlib.Path) -> Optional[pathlib.Path]:
|
|
68
68
|
|
69
69
|
|
70
70
|
def _find_local_preset(root: pathlib.Path) -> Optional[pathlib.Path]:
|
71
|
+
original_root = root
|
71
72
|
root = root.resolve()
|
72
73
|
problem_yaml_path = root / '.local.rbx' / 'preset.rbx.yml'
|
73
74
|
while root != pathlib.PosixPath('/') and not problem_yaml_path.is_file():
|
74
75
|
root = root.parent
|
75
76
|
problem_yaml_path = root / '.local.rbx' / 'preset.rbx.yml'
|
76
77
|
if not problem_yaml_path.is_file():
|
77
|
-
return _find_nested_preset(
|
78
|
+
return _find_nested_preset(original_root)
|
78
79
|
return problem_yaml_path.parent
|
79
80
|
|
80
81
|
|
@@ -326,6 +327,13 @@ def _install(root: pathlib.Path = pathlib.Path(), force: bool = False):
|
|
326
327
|
console.console.print('[error]Naming a preset "local" is prohibited.[/error]')
|
327
328
|
|
328
329
|
console.console.print(f'Installing preset [item]{preset.name}[/item]...')
|
330
|
+
installation_path = get_preset_installation_path(preset.name)
|
331
|
+
|
332
|
+
if root.resolve().is_relative_to(installation_path.resolve()):
|
333
|
+
console.console.print(
|
334
|
+
'[error]Current folder is nested into the preset installation path, cannot install it.[/error]'
|
335
|
+
)
|
336
|
+
raise typer.Exit(1)
|
329
337
|
|
330
338
|
if preset.env is not None:
|
331
339
|
console.console.print(
|
@@ -346,7 +354,6 @@ def _install(root: pathlib.Path = pathlib.Path(), force: bool = False):
|
|
346
354
|
shutil.copyfile(str(root / preset.env), get_environment_path(preset.name))
|
347
355
|
|
348
356
|
console.console.print(f'[item]{preset.name}[/item]: Copying preset folder...')
|
349
|
-
installation_path = get_preset_installation_path(preset.name)
|
350
357
|
installation_path.parent.mkdir(parents=True, exist_ok=True)
|
351
358
|
if installation_path.exists():
|
352
359
|
res = force or rich.prompt.Confirm.ask(
|
@@ -412,7 +419,7 @@ def generate_lock(
|
|
412
419
|
)
|
413
420
|
|
414
421
|
|
415
|
-
def _sync(
|
422
|
+
def _sync(try_update: bool = False):
|
416
423
|
preset_lock = get_preset_lock()
|
417
424
|
if preset_lock is None:
|
418
425
|
console.console.print(
|
@@ -423,18 +430,17 @@ def _sync(update: bool = False):
|
|
423
430
|
)
|
424
431
|
raise typer.Exit(1)
|
425
432
|
|
426
|
-
should_update =
|
433
|
+
should_update = try_update and preset_lock.uri is not None
|
427
434
|
installed_preset = get_installed_preset_or_null(preset_lock.preset_name)
|
428
435
|
if installed_preset is None:
|
429
|
-
if not
|
436
|
+
if not try_update or preset_lock.uri is None:
|
430
437
|
console.console.print(
|
431
438
|
f'[error]Preset [item]{preset_lock.preset_name}[/item] is not installed. Install it before trying to update.'
|
432
439
|
)
|
433
440
|
raise typer.Exit(1)
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
install(uri=preset_lock.uri)
|
441
|
+
install(preset_lock.uri)
|
442
|
+
elif should_update:
|
443
|
+
update(preset_lock.name)
|
438
444
|
|
439
445
|
_copy_updated_assets(
|
440
446
|
preset_lock.preset_name,
|
@@ -477,6 +483,13 @@ def update(
|
|
477
483
|
presets = [name]
|
478
484
|
|
479
485
|
for preset_name in presets:
|
486
|
+
if preset_name == LOCAL:
|
487
|
+
if not questionary.confirm(
|
488
|
+
'Updating local preset will remove all custom changes you made to the preset.',
|
489
|
+
default=False,
|
490
|
+
).ask():
|
491
|
+
continue
|
492
|
+
|
480
493
|
preset = get_installed_preset_or_null(preset_name)
|
481
494
|
if preset is None:
|
482
495
|
console.console.print(
|
@@ -490,6 +503,20 @@ def update(
|
|
490
503
|
continue
|
491
504
|
install_from_remote(preset.fetch_info, force=True)
|
492
505
|
|
506
|
+
if preset_name == LOCAL:
|
507
|
+
# Get global path to the preset.
|
508
|
+
preset_path = get_preset_installation_path(preset.name)
|
509
|
+
dest_path = '.local.rbx'
|
510
|
+
shutil.rmtree(dest_path, ignore_errors=True)
|
511
|
+
shutil.copytree(preset_path, dest_path)
|
512
|
+
console.console.print(
|
513
|
+
'[success]Local preset updated successfully.[/success]'
|
514
|
+
)
|
515
|
+
else:
|
516
|
+
console.console.print(
|
517
|
+
f'[success]Preset [item]{preset_name}[/item] updated successfully.[/success]'
|
518
|
+
)
|
519
|
+
|
493
520
|
|
494
521
|
@app.command(
|
495
522
|
'sync',
|
@@ -507,7 +534,7 @@ def sync(
|
|
507
534
|
] = False,
|
508
535
|
):
|
509
536
|
_check_is_valid_package()
|
510
|
-
_sync(
|
537
|
+
_sync(try_update=update)
|
511
538
|
|
512
539
|
|
513
540
|
@app.command(
|
rbx/box/solutions.py
CHANGED
@@ -7,11 +7,13 @@ import shutil
|
|
7
7
|
from collections.abc import Iterator
|
8
8
|
from typing import Dict, Iterable, List, Optional, Set, Tuple
|
9
9
|
|
10
|
+
import questionary
|
10
11
|
import rich
|
11
12
|
import rich.live
|
12
13
|
import rich.markup
|
13
14
|
import rich.table
|
14
15
|
import rich.text
|
16
|
+
import typer
|
15
17
|
from pydantic import BaseModel
|
16
18
|
|
17
19
|
from rbx import console
|
@@ -410,6 +412,7 @@ def run_solutions(
|
|
410
412
|
|
411
413
|
|
412
414
|
def _run_interactive_solutions(
|
415
|
+
progress: Optional[StatusProgress] = None,
|
413
416
|
tracked_solutions: Optional[Set[str]] = None,
|
414
417
|
verification: VerificationLevel = VerificationLevel.NONE,
|
415
418
|
generator: Optional[GeneratorCall] = None,
|
@@ -423,7 +426,7 @@ def _run_interactive_solutions(
|
|
423
426
|
|
424
427
|
checker_digest = checkers.compile_checker() if check else None
|
425
428
|
compiled_solutions = compile_solutions(
|
426
|
-
tracked_solutions=tracked_solutions, sanitized=sanitized
|
429
|
+
progress=progress, tracked_solutions=tracked_solutions, sanitized=sanitized
|
427
430
|
)
|
428
431
|
|
429
432
|
main_solution_digest = None
|
@@ -498,6 +501,7 @@ def _run_interactive_solutions(
|
|
498
501
|
|
499
502
|
|
500
503
|
async def run_and_print_interactive_solutions(
|
504
|
+
progress: Optional[StatusProgress] = None,
|
501
505
|
tracked_solutions: Optional[Set[str]] = None,
|
502
506
|
verification: VerificationLevel = VerificationLevel.NONE,
|
503
507
|
generator: Optional[GeneratorCall] = None,
|
@@ -507,6 +511,7 @@ async def run_and_print_interactive_solutions(
|
|
507
511
|
):
|
508
512
|
pkg = package.find_problem_package_or_die()
|
509
513
|
items = _run_interactive_solutions(
|
514
|
+
progress=progress,
|
510
515
|
tracked_solutions=tracked_solutions,
|
511
516
|
verification=verification,
|
512
517
|
check=check,
|
@@ -515,6 +520,9 @@ async def run_and_print_interactive_solutions(
|
|
515
520
|
print=print,
|
516
521
|
)
|
517
522
|
|
523
|
+
if progress:
|
524
|
+
progress.stop()
|
525
|
+
|
518
526
|
for item in items:
|
519
527
|
sol = pkg.solutions[item.solution_index]
|
520
528
|
_print_solution_header(sol, console.console, is_irun=True)
|
@@ -540,6 +548,31 @@ async def run_and_print_interactive_solutions(
|
|
540
548
|
console.console.print()
|
541
549
|
|
542
550
|
|
551
|
+
def _get_solution_repr(sol: Solution) -> List[Tuple[str, str]]:
|
552
|
+
return [
|
553
|
+
('', f'{str(sol.path)} '),
|
554
|
+
(f'fg:{sol.outcome.style().replace('lnumber', 'cyan')}', sol.outcome.name),
|
555
|
+
]
|
556
|
+
|
557
|
+
|
558
|
+
def pick_solutions(tracked_solutions: Optional[Set[str]]) -> List[str]:
|
559
|
+
pkg = package.find_problem_package_or_die()
|
560
|
+
if tracked_solutions is None:
|
561
|
+
tracked_solutions = set(str(sol.path) for sol in pkg.solutions)
|
562
|
+
|
563
|
+
# Store in a separate list to maintain order with the package declaration.
|
564
|
+
choices = [
|
565
|
+
questionary.Choice(title=_get_solution_repr(sol), value=str(sol.path))
|
566
|
+
for sol in pkg.solutions
|
567
|
+
if str(sol.path) in tracked_solutions
|
568
|
+
]
|
569
|
+
|
570
|
+
picked = questionary.checkbox('Select solutions', choices=choices).ask()
|
571
|
+
if picked is None:
|
572
|
+
raise typer.Abort()
|
573
|
+
return picked
|
574
|
+
|
575
|
+
|
543
576
|
def get_outcome_style_verdict(outcome: Outcome) -> str:
|
544
577
|
if outcome == Outcome.ACCEPTED:
|
545
578
|
return 'green'
|
@@ -554,21 +587,25 @@ def get_outcome_style_verdict(outcome: Outcome) -> str:
|
|
554
587
|
return 'magenta'
|
555
588
|
|
556
589
|
|
557
|
-
def
|
590
|
+
def get_outcome_markup_verdict(outcome: Outcome) -> str:
|
558
591
|
res = '✓'
|
559
|
-
if
|
592
|
+
if outcome != Outcome.ACCEPTED:
|
560
593
|
res = '✗'
|
561
|
-
if
|
594
|
+
if outcome == Outcome.TIME_LIMIT_EXCEEDED:
|
562
595
|
res = '⧖'
|
563
|
-
if
|
596
|
+
if outcome == Outcome.RUNTIME_ERROR:
|
564
597
|
res = '✗'
|
565
|
-
style = get_outcome_style_verdict(
|
598
|
+
style = get_outcome_style_verdict(outcome)
|
566
599
|
res = f'[{style}]{res}[/{style}]'
|
600
|
+
return res
|
601
|
+
|
602
|
+
|
603
|
+
def get_testcase_markup_verdict(eval: Evaluation) -> str:
|
567
604
|
# if eval.log.stdout_absolute_path:
|
568
605
|
# output_path = eval.log.stdout_absolute_path.resolve()
|
569
606
|
# output_link = f'file://{output_path}'
|
570
607
|
# res = f'[link={output_link}]{res}[/link]'
|
571
|
-
return
|
608
|
+
return get_outcome_markup_verdict(eval.result.outcome)
|
572
609
|
|
573
610
|
|
574
611
|
def _get_evals_time_in_ms(evals: List[Evaluation]) -> int:
|
@@ -632,6 +669,10 @@ def get_evals_formatted_memory(evals: List[Evaluation]) -> str:
|
|
632
669
|
return get_formatted_memory(max_memory)
|
633
670
|
|
634
671
|
|
672
|
+
def get_worst_outcome(evals: List[Evaluation]) -> Outcome:
|
673
|
+
return Outcome.worst_outcome(eval.result.outcome for eval in evals)
|
674
|
+
|
675
|
+
|
635
676
|
def _print_solution_outcome(
|
636
677
|
solution: Solution,
|
637
678
|
evals: List[Evaluation],
|
@@ -880,7 +921,11 @@ async def _render_detailed_group_table(
|
|
880
921
|
solution, non_null_evals, verification
|
881
922
|
)
|
882
923
|
formatted_memory = get_evals_formatted_memory(non_null_evals)
|
883
|
-
|
924
|
+
worst_outcome = get_worst_outcome(non_null_evals)
|
925
|
+
verdict = get_outcome_markup_verdict(worst_outcome)
|
926
|
+
summary_row.append(
|
927
|
+
('', verdict, formatted_time, '/', formatted_memory, '')
|
928
|
+
)
|
884
929
|
padded_rows.append(summary_row)
|
885
930
|
|
886
931
|
for row in _render_padded_rows(padded_rows):
|
@@ -1020,12 +1065,13 @@ async def print_run_report(
|
|
1020
1065
|
)
|
1021
1066
|
console.print()
|
1022
1067
|
|
1023
|
-
|
1068
|
+
cur_ok = _print_solution_outcome(
|
1024
1069
|
solution,
|
1025
1070
|
solution_evals,
|
1026
1071
|
console,
|
1027
1072
|
verification=verification,
|
1028
1073
|
)
|
1074
|
+
ok = ok and cur_ok
|
1029
1075
|
console.print()
|
1030
1076
|
|
1031
1077
|
await _print_timing(console, result.skeleton, structured_evaluations)
|
rbx/box/validators.py
CHANGED
@@ -262,10 +262,22 @@ def print_validation_report(infos: List[TestcaseValidationInfo]):
|
|
262
262
|
if not _has_group_specific_validator():
|
263
263
|
hit_bounds_per_group = {None: _merge_hit_bounds(hit_bounds_per_group.values())}
|
264
264
|
|
265
|
+
def _is_hit_bound_good(hit_bounds: HitBounds) -> bool:
|
266
|
+
return any(not v[0] or not v[1] for v in hit_bounds.values())
|
267
|
+
|
268
|
+
# Cleanup entries in hit bounds per group that are totally empty.
|
269
|
+
# Also skip samples.
|
270
|
+
hit_bounds_per_group = {
|
271
|
+
k: v
|
272
|
+
for k, v in hit_bounds_per_group.items()
|
273
|
+
if _is_hit_bound_good(v) and k != 'samples'
|
274
|
+
}
|
275
|
+
|
276
|
+
if not hit_bounds_per_group:
|
277
|
+
console.console.print('[info]No validation issues found.[/info]')
|
278
|
+
return
|
279
|
+
|
265
280
|
for group, hit_bounds in hit_bounds_per_group.items():
|
266
|
-
if group == 'samples':
|
267
|
-
# Skip samples.
|
268
|
-
continue
|
269
281
|
if group is None:
|
270
282
|
console.console.print('Hit bounds:')
|
271
283
|
else:
|
rbx/console.py
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
import sys
|
2
2
|
|
3
|
+
import rich
|
4
|
+
import rich.markup
|
3
5
|
from rich.console import Console
|
4
6
|
from rich.theme import Theme
|
5
7
|
|
@@ -25,3 +27,13 @@ def multiline_prompt(text: str) -> str:
|
|
25
27
|
lines = sys.stdin.readlines()
|
26
28
|
console.print()
|
27
29
|
return ''.join(lines)
|
30
|
+
|
31
|
+
|
32
|
+
def render_from(r: rich.console.RenderableType) -> str:
|
33
|
+
with console.capture() as capture:
|
34
|
+
console.print(r)
|
35
|
+
return capture.get()
|
36
|
+
|
37
|
+
|
38
|
+
def render_from_markup(markup: str) -> str:
|
39
|
+
return render_from(rich.markup.render(markup))
|
rbx/grading/steps.py
CHANGED
@@ -6,7 +6,7 @@ import shutil
|
|
6
6
|
import subprocess
|
7
7
|
import sys
|
8
8
|
from enum import Enum
|
9
|
-
from typing import IO, Any, Dict, List, Optional, Tuple, Union
|
9
|
+
from typing import IO, Any, Dict, Iterable, List, Optional, Tuple, Union
|
10
10
|
|
11
11
|
import typer
|
12
12
|
from pydantic import BaseModel
|
@@ -24,13 +24,20 @@ MAX_STDOUT_LEN = 1024 * 1024 * 128 # 128 MB
|
|
24
24
|
class Outcome(Enum):
|
25
25
|
ACCEPTED = 'accepted'
|
26
26
|
WRONG_ANSWER = 'wrong-answer'
|
27
|
-
JUDGE_FAILED = 'judge-failed'
|
28
|
-
RUNTIME_ERROR = 'runtime-error'
|
29
|
-
TIME_LIMIT_EXCEEDED = 'time-limit-exceeded'
|
30
27
|
MEMORY_LIMIT_EXCEEDED = 'memory-limit-exceeded'
|
28
|
+
TIME_LIMIT_EXCEEDED = 'time-limit-exceeded'
|
29
|
+
RUNTIME_ERROR = 'runtime-error'
|
31
30
|
OUTPUT_LIMIT_EXCEEDED = 'output-limit-exceeded'
|
31
|
+
JUDGE_FAILED = 'judge-failed'
|
32
32
|
INTERNAL_ERROR = 'internal-error'
|
33
33
|
|
34
|
+
@classmethod
|
35
|
+
def worst_outcome(cls, outcomes: Iterable['Outcome']) -> 'Outcome':
|
36
|
+
def _outcome_to_int(o: 'Outcome') -> int:
|
37
|
+
return cls._member_names_.index(o.name)
|
38
|
+
|
39
|
+
return max(outcomes, key=_outcome_to_int)
|
40
|
+
|
34
41
|
|
35
42
|
class DigestHolder(BaseModel):
|
36
43
|
value: Optional[str] = None
|
@@ -5,14 +5,14 @@ rbx/box/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
rbx/box/builder.py,sha256=0TiRQJoHqLHAI8QwBrscbaJmhdcmialVtP_oEkfHcs0,3260
|
6
6
|
rbx/box/cd.py,sha256=9a_SOnzoJBXxxffp4Wbf3UKXIwKuN3Hvj7K6SocALwE,1194
|
7
7
|
rbx/box/checkers.py,sha256=VpgDzevOK7hrffG2zJGxquNiu-a9Fl3wquLn7xadcK0,6285
|
8
|
-
rbx/box/code.py,sha256=
|
8
|
+
rbx/box/code.py,sha256=N2pjerRsESDqQ0G8NNeUJQgXxlJTZwNmanhhXZn5tN8,11531
|
9
9
|
rbx/box/compile.py,sha256=OJLthDQ921w9vyoE6Gk1Df54i5RwtRJ2YG-8XEfefcs,2489
|
10
10
|
rbx/box/conftest.py,sha256=sEmciXSeDC-wmrZ1JSxbsUenKNP_VWW32mrCun2pY3I,1070
|
11
11
|
rbx/box/contest/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
12
12
|
rbx/box/contest/build_contest_statements.py,sha256=OvKTE2O1UchiBRPzbU94KdIn0whT00VUbyPzUDlJRJg,11290
|
13
13
|
rbx/box/contest/contest_package.py,sha256=OaUbpBtkhkgOPzJ1ccI_Vq4FMSaJvZm3gMOKfVY8oy4,3032
|
14
14
|
rbx/box/contest/contest_utils.py,sha256=TDE7I6YQJlu4dQd68wzOp019bNgqiT0RlM-LMQMjL9w,301
|
15
|
-
rbx/box/contest/main.py,sha256=
|
15
|
+
rbx/box/contest/main.py,sha256=fFYeZn8KTLt9j-OFFkcBeQWw8RsTLUauFV6drM2hT48,7404
|
16
16
|
rbx/box/contest/schema.py,sha256=rxzjJasMWPKKhvSJs4eW4A2oCiA4gXgfF-MzqsbPslQ,4914
|
17
17
|
rbx/box/contest/statements.py,sha256=Pe4uo1hxvEON8O11VAzsOP3DxUel0vmwiAmolh4ltEs,2910
|
18
18
|
rbx/box/creation.py,sha256=mVHVVj8ozX9D5qpkLewE5WSjF6HtTm74Pkwubk-bATg,2259
|
@@ -22,7 +22,7 @@ rbx/box/environment.py,sha256=47NtyuVC6zSQKAtQaXPEXvqcD-KJiuWRpWF8pYvcG4c,11158
|
|
22
22
|
rbx/box/extensions.py,sha256=gIC73VbF1897er3iIMhaIw6GE8o1t43M7q97Iz7-_lg,503
|
23
23
|
rbx/box/generators.py,sha256=bzURQrNEscCO8_3BYXCyGK9ZneX4-eOJZP5JJV28f3I,16350
|
24
24
|
rbx/box/generators_test.py,sha256=mQqHepAMYa6zV_PseQALI0nIX6AdQktt6lh94muFhNw,1758
|
25
|
-
rbx/box/main.py,sha256=
|
25
|
+
rbx/box/main.py,sha256=PZYbC2k6f2pMIuj1-X_yEANCWYstkIXJ7Bg0Szi37YA,23293
|
26
26
|
rbx/box/package.py,sha256=SSckCXo7Zh412_qjYhSNwConjhR0Sk8911qGJU34Hac,11851
|
27
27
|
rbx/box/packaging/boca/extension.py,sha256=hQhcbocNfW2ESv5RalS1wf6uvOoOfOnR_gHvbXUbSzY,852
|
28
28
|
rbx/box/packaging/boca/packager.py,sha256=FOhSRg5K5Y4qNB0WyTR3DKgrpObf9I0JbyGpJHOtxpo,10673
|
@@ -32,14 +32,14 @@ rbx/box/packaging/packager.py,sha256=suCT_SLnWa915rV2j8VFqzH43HGKRTr9mGGlrvj45aw
|
|
32
32
|
rbx/box/packaging/polygon/packager.py,sha256=HNpxP2nclLChSnrQtkT7tLwDdXHl1dzXMIF5RZwr9M4,10811
|
33
33
|
rbx/box/packaging/polygon/test.py,sha256=bgEju5PwudgyfwxXJagm8fM6CJVlWM6l_-2q1V-oKaQ,3069
|
34
34
|
rbx/box/packaging/polygon/xml_schema.py,sha256=-r24bCeRMGLrGGoT9FIgmqr87xHL-JzrFaR6bztbYtw,2703
|
35
|
-
rbx/box/presets/__init__.py,sha256=
|
35
|
+
rbx/box/presets/__init__.py,sha256=jivkMH5lWuNpwg4HExpacgPo7RqaENqmrE86aq3CJ6A,18643
|
36
36
|
rbx/box/presets/fetch.py,sha256=F-BCOlvEBEyDqtOhiDuGPn4EDtA4Bwm-fqHJ7zZGlW8,1975
|
37
37
|
rbx/box/presets/lock_schema.py,sha256=6sRPnyePOC8yy-5WcD5JRZdDJHf8loqbvpQ1IPiOU9s,349
|
38
38
|
rbx/box/presets/schema.py,sha256=mZmSPkQsw7eQM0lQN6er1MO_LiW1ObwwAZFDK0F5fxE,1962
|
39
39
|
rbx/box/sanitizers/warning_stack.py,sha256=RI97_GJgdjTKIXY_r0EKp5h0qQQSDSdNDh5K7zINrqs,2861
|
40
40
|
rbx/box/schema.py,sha256=mPEOchzoGDwk_S9wUw1DKqwJWJ0S5GTxQnZIlm9BFwo,13709
|
41
41
|
rbx/box/setter_config.py,sha256=6nGTPMvnJ7y1sM-EBuI493NSZOIiOZ1DTypSXrL-HRY,3686
|
42
|
-
rbx/box/solutions.py,sha256=
|
42
|
+
rbx/box/solutions.py,sha256=GmjWlFDHZSHk44zGnISMEua1z3PE7XqjjdqHRaPSDG8,38041
|
43
43
|
rbx/box/solutions_test.py,sha256=Cx7Goon_0sz_PaUcD8qa8gmjgzOVub6VHss3CB0GaA0,1524
|
44
44
|
rbx/box/statements/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
45
45
|
rbx/box/statements/build_statements.py,sha256=upsMT-cAnSvbmKgtijdFc0OxPcyeBxRG92hY6dN-ZOk,11920
|
@@ -58,13 +58,13 @@ rbx/box/ui/captured_log.py,sha256=ptICDPViVnz-_2NfrcB0SSBXNW5L74zI-vAZNN7kSok,11
|
|
58
58
|
rbx/box/ui/css/app.tcss,sha256=apd5PkPEvl5jK3kE2qrxPyVED1VnvSsj08QQwzUPwEA,786
|
59
59
|
rbx/box/ui/main.py,sha256=b0rHcBF42W4AOCv7WhtiGf_rUnY0yxpqO5oj3wfR4R4,984
|
60
60
|
rbx/box/ui/run.py,sha256=wMEXrEFdQvMHz2hRKAFIithTnTtaL0kNQZu0jKmb8jI,7060
|
61
|
-
rbx/box/validators.py,sha256=
|
61
|
+
rbx/box/validators.py,sha256=SNOzOk2lJdxnU3e6TFPaqaBwz-qisYLR44vbiH1F4Hc,8806
|
62
62
|
rbx/box/validators_test.py,sha256=hriR6rD32Ouu64eKYYTPLZVvqMxXj7Q2h1l_JAefL7U,344
|
63
63
|
rbx/checker.py,sha256=pj1jO3my48ru-qugbER5onccANCjoR0-PaFe3H3VGEY,4118
|
64
64
|
rbx/clone.py,sha256=wpHyED0_7ST7LD3vj7HjXhzqEzlwh6dRQvKQVDYhGeU,6744
|
65
65
|
rbx/config.py,sha256=gM0-3ORnCoXNpIxIA3EAXEaiNY7s_NPzD7WbFYGbrlA,7436
|
66
66
|
rbx/conftest.py,sha256=ouilbOIpvX8jTEdCAiWT85CbdBQKUUf41BjmDI82u-Y,967
|
67
|
-
rbx/console.py,sha256=
|
67
|
+
rbx/console.py,sha256=X8EJy68OROgh6ao3ZcUjZm5Y56VFMzen58ywAuQ7pAU,990
|
68
68
|
rbx/create.py,sha256=ezUq9KiSA-88ASd8CtjWXw8UB4LCaQ3Gib3OgvsLK-Q,986
|
69
69
|
rbx/edit.py,sha256=Zqnx_Pt06ijCxV-pZKGCJhjKB-nVO0QCM6xSBwPWGoE,798
|
70
70
|
rbx/grading/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -81,7 +81,7 @@ rbx/grading/judge/sandboxes/timeit.py,sha256=xScfasI2lsSQGZVpIZ7qBZfi0IaKC-1k8wO
|
|
81
81
|
rbx/grading/judge/storage.py,sha256=FirqjwDqb0m0h2OTFyWrZL7CQ4XjZNxhqB4JpnDIhZY,9485
|
82
82
|
rbx/grading/judge/test.py,sha256=ll0Iw7zyOpGdKPD_PGH7dvUkb4stQLu-ikbQnqJvuAc,944
|
83
83
|
rbx/grading/judge/testiso.py,sha256=v14DtkWiZFJ9AKMzrb0_vZKPWDt8jz8iIw1Z2O-Advk,1397
|
84
|
-
rbx/grading/steps.py,sha256=
|
84
|
+
rbx/grading/steps.py,sha256=EKH9k5buU69f8wgek6rAk72oAnDJ22nOp2Rw-touk_Q,23037
|
85
85
|
rbx/grading/steps_with_caching.py,sha256=5cI71VSjEaDzCPQikpamG_EjZqkkFKKdBtyJeA4QB7Q,1531
|
86
86
|
rbx/grading/steps_with_caching_run_test.py,sha256=nRzB4OcXkb-kQ4WCj0iTGVfBACllxZ0Ek5RSwfoJRgo,15262
|
87
87
|
rbx/grading_utils.py,sha256=lL2KtSkOsMElqrRoApQTbFcqVOeHVWUDTMCa3IsLpC4,4484
|
@@ -162,8 +162,8 @@ rbx/testdata/caching/executable.py,sha256=WKRHNf_fprFJd1Fq1ubmQtR3mZzTYVNwKPLWuZ
|
|
162
162
|
rbx/testdata/compatible,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
163
163
|
rbx/testing_utils.py,sha256=ZZLKMUHlZ4HwsuNY50jqSBJ9HhpnFdba7opjDsvXE1U,2084
|
164
164
|
rbx/utils.py,sha256=WlmnF4whc0-6ksVZoOhmom2bR2spT6zETFHjnpJOCsA,4383
|
165
|
-
rbx_cp-0.5.
|
166
|
-
rbx_cp-0.5.
|
167
|
-
rbx_cp-0.5.
|
168
|
-
rbx_cp-0.5.
|
169
|
-
rbx_cp-0.5.
|
165
|
+
rbx_cp-0.5.26.dist-info/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
166
|
+
rbx_cp-0.5.26.dist-info/METADATA,sha256=89qCk1_R5uGy7JXZieXOmYMBb4PY1YI4L94gxlsRq40,3290
|
167
|
+
rbx_cp-0.5.26.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
168
|
+
rbx_cp-0.5.26.dist-info/entry_points.txt,sha256=qBTLBOeifT1F00LWaEewRRE_jQPgvH7BUdJfZ-dYsFU,57
|
169
|
+
rbx_cp-0.5.26.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|