rbx.cp 0.5.28__py3-none-any.whl → 0.5.30__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.
@@ -19,7 +19,7 @@ def test_generator_works(pkg_from_testdata: pathlib.Path):
19
19
  print_directory_tree(pkg_from_testdata)
20
20
 
21
21
  assert (
22
- package.get_build_testgroup_path('gen1') / '0-main-000.in'
22
+ package.get_build_testgroup_path('gen1') / '0-000.in'
23
23
  ).read_text() == '777\n'
24
24
  assert (
25
25
  package.get_build_testgroup_path('gen1') / '1-gen-000.in'
rbx/box/main.py CHANGED
@@ -25,6 +25,7 @@ from rbx.box import (
25
25
  builder,
26
26
  cd,
27
27
  setter_config,
28
+ state,
28
29
  creation,
29
30
  download,
30
31
  environment,
@@ -48,6 +49,7 @@ from rbx.box.solutions import (
48
49
  run_solutions,
49
50
  )
50
51
  from rbx.box.statements import build_statements
52
+ from rbx.box.testcases import TestcaseEntry
51
53
 
52
54
  app = typer.Typer(no_args_is_help=True, cls=annotations.AliasGroup)
53
55
  app.add_typer(
@@ -82,6 +84,11 @@ app.add_typer(
82
84
  )
83
85
 
84
86
 
87
+ @app.callback()
88
+ def main():
89
+ state.STATE.run_through_cli = True
90
+
91
+
85
92
  # @app.command('ui', hidden=True)
86
93
  # @package.within_problem
87
94
  # def ui():
@@ -254,6 +261,7 @@ def _time_impl(check: bool, detailed: bool) -> Optional[int]:
254
261
  tracked_solutions=tracked_solutions,
255
262
  check=check,
256
263
  verification=VerificationLevel(verification),
264
+ timelimit_override=600, # 10 minute time limit for estimation
257
265
  )
258
266
 
259
267
  console.console.print()
@@ -343,6 +351,14 @@ def irun(
343
351
  '-g',
344
352
  help='Generator call to use to generate a single test for execution.',
345
353
  ),
354
+ testcase: Optional[str] = typer.Option(
355
+ None,
356
+ '--testcase',
357
+ '--test',
358
+ '-tc',
359
+ '-t',
360
+ help='Testcase to run, in the format "[group]/[index]". If not specified, will run interactively.',
361
+ ),
346
362
  print: bool = typer.Option(
347
363
  False, '--print', '-p', help='Whether to print outputs to terminal.'
348
364
  ),
@@ -370,13 +386,6 @@ def irun(
370
386
  )
371
387
  return
372
388
 
373
- main_solution = package.get_main_solution()
374
- if check and main_solution is None:
375
- console.console.print(
376
- '[warning]No main solution found, running without checkers.[/warning]'
377
- )
378
- check = False
379
-
380
389
  tracked_solutions = None
381
390
  if outcome is not None:
382
391
  tracked_solutions = {
@@ -411,6 +420,7 @@ def irun(
411
420
  generator=generators.get_call_from_string(generator)
412
421
  if generator is not None
413
422
  else None,
423
+ testcase_entry=TestcaseEntry.parse(testcase) if testcase else None,
414
424
  print=print,
415
425
  sanitized=sanitized,
416
426
  )
rbx/box/schema.py CHANGED
@@ -20,6 +20,12 @@ def NameField(**kwargs):
20
20
  )
21
21
 
22
22
 
23
+ def FNameField(**kwargs):
24
+ return Field(
25
+ pattern=r'^[a-zA-Z0-9][a-zA-Z0-9\-_]*$', min_length=3, max_length=128, **kwargs
26
+ )
27
+
28
+
23
29
  def _check_oneof(model_obj: BaseModel, fields: List[str]):
24
30
  has = []
25
31
  for field in fields:
@@ -100,9 +106,9 @@ class ExpectedOutcome(AutoEnum):
100
106
  if self.match(Outcome.TIME_LIMIT_EXCEEDED):
101
107
  return 'yellow'
102
108
  if self.match(Outcome.RUNTIME_ERROR):
103
- return 'lnumber'
109
+ return 'blue'
104
110
  if self.match(Outcome.MEMORY_LIMIT_EXCEEDED):
105
- return 'cyan'
111
+ return 'yellow'
106
112
  return 'magenta'
107
113
 
108
114
  def is_slow(self) -> bool:
@@ -150,11 +156,11 @@ class CodeItem(BaseModel):
150
156
  )
151
157
 
152
158
  language: Optional[str] = Field(
153
- None, description="""The language of the code file."""
159
+ default=None, description="""The language of the code file."""
154
160
  )
155
161
 
156
162
  compilationFiles: Optional[List[str]] = Field(
157
- [],
163
+ default=[],
158
164
  description="""
159
165
  Extra files that should be placed alongside the code file during its compilation,
160
166
  such as testlib.h, jngen.h, etc.
@@ -173,17 +179,17 @@ class Testcase(BaseModel):
173
179
  inputPath: pathlib.Path = Field(description="""The path of the input file.""")
174
180
 
175
181
  outputPath: Optional[pathlib.Path] = Field(
176
- None, description="""The path of the output file."""
182
+ default=None, description="""The path of the output file."""
177
183
  )
178
184
 
179
185
 
180
186
  class GeneratorCall(BaseModel):
181
187
  model_config = ConfigDict(extra='forbid')
182
188
 
183
- name: str = NameField(description='The name of the generator to call.')
189
+ name: str = FNameField(description='The name of the generator to call.')
184
190
 
185
191
  args: Optional[str] = Field(
186
- None, description='The arguments to pass to the generator.'
192
+ default=None, description='The arguments to pass to the generator.'
187
193
  )
188
194
 
189
195
 
@@ -193,31 +199,31 @@ class TestcaseSubgroup(BaseModel):
193
199
  name: str = NameField(description='The name of the test group.')
194
200
 
195
201
  testcases: List[Testcase] = Field(
196
- [],
202
+ default=[],
197
203
  description="""
198
204
  The path of testcases to add to this group,
199
205
  in the order they're defined.""",
200
206
  )
201
207
 
202
208
  testcaseGlob: Optional[str] = Field(
203
- None,
209
+ default=None,
204
210
  description="""
205
211
  A Python glob that matches input file paths relative to the
206
212
  package directory. The globbed files should end with the extension
207
213
  ".in", and their corresponding outputs, if defined, should have the same file name,
208
- but ending with ".out".
214
+ but ending with ".ans".
209
215
  """,
210
216
  )
211
217
 
212
218
  generators: List[GeneratorCall] = Field(
213
- [],
219
+ default=[],
214
220
  description="""
215
221
  A list of generators to call to generate testcases for this group.
216
222
  """,
217
223
  )
218
224
 
219
225
  generatorScript: Optional[CodeItem] = Field(
220
- None,
226
+ default=None,
221
227
  description="""
222
228
  A generator script to call to generate testcases for this group.
223
229
  """,
@@ -241,14 +247,14 @@ class TestcaseGroup(TestcaseSubgroup):
241
247
  model_config = ConfigDict(extra='forbid')
242
248
 
243
249
  subgroups: List[TestcaseSubgroup] = Field(
244
- [],
250
+ default=[],
245
251
  description="""
246
252
  A list of test subgroups to define for this group.
247
253
  """,
248
254
  )
249
255
 
250
256
  validator: Optional[CodeItem] = Field(
251
- None,
257
+ default=None,
252
258
  description="""
253
259
  A validator to use to validate the testcases of this group.
254
260
  If not specified, will use the package-level validator.
@@ -257,7 +263,7 @@ Useful in cases where the constraints vary across test groups.
257
263
  )
258
264
 
259
265
  weight: Optional[float] = Field(
260
- 1.0,
266
+ default=1.0,
261
267
  description="""
262
268
  The weight of this group in the final score. Useful for
263
269
  problems that have points.
@@ -295,29 +301,29 @@ class Stress(BaseModel):
295
301
 
296
302
  class Limits(BaseModel):
297
303
  time: Optional[int] = Field(
298
- None, description='Value to override time limit with, in milliseconds.'
304
+ default=None, description='Value to override time limit with, in milliseconds.'
299
305
  )
300
306
  memory: Optional[int] = Field(
301
- None, description='Value to override memory limit with, in MB.'
307
+ default=None, description='Value to override memory limit with, in MB.'
302
308
  )
303
309
  output: Optional[int] = Field(
304
- None, description='Value to override output limit with, in KB.'
310
+ default=None, description='Value to override output limit with, in KB.'
305
311
  )
306
312
 
307
313
  isDoubleTL: bool = Field(
308
- False, description='Whether to use double TL for this language.'
314
+ default=False, description='Whether to use double TL for this language.'
309
315
  )
310
316
 
311
317
 
312
318
  class LimitModifiers(BaseModel):
313
319
  timeMultiplier: Optional[float] = Field(
314
- None, description='Multiplier for time limit.'
320
+ default=None, description='Multiplier for time limit.'
315
321
  )
316
322
  time: Optional[int] = Field(
317
- None, description='Value to override time limit with, in milliseconds.'
323
+ default=None, description='Value to override time limit with, in milliseconds.'
318
324
  )
319
325
  memory: Optional[int] = Field(
320
- None, description='Value to override memory limit with, in MB.'
326
+ default=None, description='Value to override memory limit with, in MB.'
321
327
  )
322
328
 
323
329
 
@@ -332,28 +338,30 @@ class Package(BaseModel):
332
338
  memoryLimit: int = Field(description='Memory limit of the problem, in MB.')
333
339
 
334
340
  outputLimit: int = Field(
335
- 4 * 1024, description='Output limit of the problem, in KB.'
341
+ default=4 * 1024, description='Output limit of the problem, in KB.'
336
342
  )
337
343
 
338
344
  modifiers: Dict[str, LimitModifiers] = Field(
339
- {},
345
+ default={},
340
346
  description="""
341
347
  Limit modifiers that can be specified per language.
342
348
  """,
343
349
  )
344
350
 
345
351
  checker: Optional[CodeItem] = Field(
346
- None, description='The checker for this problem.'
352
+ default=None, description='The checker for this problem.'
347
353
  )
348
354
 
349
355
  validator: Optional[CodeItem] = Field(
350
- None, description='The validator for this problem.'
356
+ default=None, description='The validator for this problem.'
351
357
  )
352
358
 
353
- generators: List[Generator] = Field([], description='Generators for this problem.')
359
+ generators: List[Generator] = Field(
360
+ default=[], description='Generators for this problem.'
361
+ )
354
362
 
355
363
  solutions: List[Solution] = Field(
356
- [],
364
+ default=[],
357
365
  description="""
358
366
  All tested solutions for this problem.
359
367
 
@@ -362,17 +370,23 @@ that is correct and used as reference -- and should have the `accepted` outcome.
362
370
  """,
363
371
  )
364
372
 
365
- testcases: List[TestcaseGroup] = Field([], description='Testcases for the problem.')
373
+ testcases: List[TestcaseGroup] = Field(
374
+ default=[], description='Testcases for the problem.'
375
+ )
366
376
 
367
- stresses: List[Stress] = Field([], description='Stress tests for the problem.')
377
+ stresses: List[Stress] = Field(
378
+ default=[], description='Stress tests for the problem.'
379
+ )
368
380
 
369
- statements: List[Statement] = Field([], description='Statements for the problem.')
381
+ statements: List[Statement] = Field(
382
+ default=[], description='Statements for the problem.'
383
+ )
370
384
 
371
385
  # Vars to be re-used across the package.
372
386
  # - It will be passed as --key=value arguments to the validator.
373
387
  # - It will be available as \VAR{key} variables in the rbx statement.
374
388
  vars: Dict[str, Primitive] = Field(
375
- {}, description='Variables to be re-used across the package.'
389
+ default={}, description='Variables to be re-used across the package.'
376
390
  )
377
391
 
378
392
  @property
@@ -403,12 +417,15 @@ that is correct and used as reference -- and should have the `accepted` outcome.
403
417
  return res
404
418
 
405
419
  @model_validator(mode='after')
406
- def check_first_solution_is_main(self):
420
+ def check_first_solution_is_main_if_there_is_ac(self):
421
+ if all(sol.outcome != Outcome.ACCEPTED for sol in self.solutions):
422
+ # No main solution.
423
+ return self
407
424
  if self.solutions:
408
425
  if self.solutions[0].outcome != ExpectedOutcome.ACCEPTED:
409
426
  raise PydanticCustomError(
410
427
  'MISSING_MAIN_SOLUTION',
411
- 'The first solution in the package must have the "ACCEPTED" outcome.',
428
+ 'The first solution in the package must have the "ACCEPTED" outcome if there are ACCEPTED solutions.',
412
429
  )
413
430
  return self
414
431
 
rbx/box/setter_config.py CHANGED
@@ -19,36 +19,36 @@ _CONFIG_FILE_NAME_MAC = 'default_setter_config.mac.yml'
19
19
 
20
20
  class SanitizersConfig(BaseModel):
21
21
  enabled: bool = Field(
22
- False,
22
+ default=False,
23
23
  description='Whether to use sanitizers when running solutions.',
24
24
  )
25
25
 
26
26
  command_substitutions: Dict[str, str] = Field(
27
- {},
27
+ default={},
28
28
  description='Substitutions to apply to commands before running them with sanitizers.',
29
29
  )
30
30
 
31
31
 
32
32
  class WarningsConfig(BaseModel):
33
33
  enabled: bool = Field(
34
- False,
34
+ default=False,
35
35
  description='Whether to use warning flags when running solutions.',
36
36
  )
37
37
 
38
38
 
39
39
  class RepeatsConfig(BaseModel):
40
40
  reps: int = Field(
41
- 1,
41
+ default=1,
42
42
  description='Number of times to repeat the solution.',
43
43
  )
44
44
 
45
45
  retries: int = Field(
46
- 0,
46
+ default=0,
47
47
  description='Number of times to retry if the solution TLs.',
48
48
  )
49
49
 
50
50
  retries_for_stress: int = Field(
51
- 0,
51
+ default=0,
52
52
  description='Number of times to retry in stress mode if the solution TLs.',
53
53
  )
54
54
 
@@ -69,7 +69,7 @@ class SetterConfig(BaseModel):
69
69
  )
70
70
 
71
71
  command_substitutions: Dict[str, str] = Field(
72
- {},
72
+ default={},
73
73
  description='Substitutions to apply to commands before running them.',
74
74
  )
75
75
 
rbx/box/solutions.py CHANGED
@@ -16,7 +16,7 @@ import rich.text
16
16
  import typer
17
17
  from pydantic import BaseModel
18
18
 
19
- from rbx import console
19
+ from rbx import console, utils
20
20
  from rbx.box import checkers, package
21
21
  from rbx.box.code import SanitizationLevel, compile_item, find_language_name, run_item
22
22
  from rbx.box.deferred import Deferred
@@ -25,7 +25,14 @@ from rbx.box.environment import (
25
25
  ExecutionConfig,
26
26
  VerificationLevel,
27
27
  )
28
- from rbx.box.generators import generate_output_for_testcase, generate_standalone
28
+ from rbx.box.formatting import get_formatted_memory, get_formatted_time
29
+ from rbx.box.generators import (
30
+ GenerationMetadata,
31
+ expand_generator_call,
32
+ extract_generation_testcases,
33
+ generate_output_for_testcase,
34
+ generate_standalone,
35
+ )
29
36
  from rbx.box.retries import Retrier
30
37
  from rbx.box.schema import (
31
38
  ExpectedOutcome,
@@ -35,7 +42,7 @@ from rbx.box.schema import (
35
42
  Testcase,
36
43
  TestcaseGroup,
37
44
  )
38
- from rbx.box.testcases import find_built_testcases
45
+ from rbx.box.testcases import TestcaseEntry, find_built_testcases
39
46
  from rbx.grading.steps import (
40
47
  DigestOrDest,
41
48
  DigestOrSource,
@@ -419,26 +426,97 @@ def run_solutions(
419
426
  )
420
427
 
421
428
 
422
- def _run_interactive_solutions(
429
+ async def _generate_testcase_interactively(
423
430
  progress: Optional[StatusProgress] = None,
424
- tracked_solutions: Optional[Set[str]] = None,
425
- verification: VerificationLevel = VerificationLevel.NONE,
426
431
  generator: Optional[GeneratorCall] = None,
432
+ testcase_entry: Optional[TestcaseEntry] = None,
427
433
  check: bool = True,
428
- print: bool = False,
429
434
  sanitized: bool = False,
430
- ) -> Iterator[EvaluationItem]:
431
- pkg = package.find_problem_package_or_die()
435
+ print: bool = False,
436
+ ) -> Testcase:
432
437
  main_solution = package.get_main_solution()
433
- check = check and main_solution is not None
434
-
435
- checker_digest = checkers.compile_checker() if check else None
436
- compiled_solutions = compile_solutions(
437
- progress=progress, tracked_solutions=tracked_solutions, sanitized=sanitized
438
+ irun_dir = package.get_problem_iruns_dir()
439
+ inputs_dir = irun_dir / 'inputs'
440
+ inputs_dir.mkdir(parents=True, exist_ok=True)
441
+ testcase = Testcase(
442
+ inputPath=inputs_dir / '000.in',
443
+ outputPath=(inputs_dir / '000.out') if check else None,
438
444
  )
439
445
 
446
+ is_manual = False
447
+ generation_metadata = None
448
+ if generator is not None:
449
+ generation_metadata = GenerationMetadata(
450
+ generator_call=expand_generator_call(generator),
451
+ copied_to=testcase,
452
+ )
453
+ elif testcase_entry is not None:
454
+ extracted = extract_generation_testcases([testcase_entry])
455
+ if not extracted:
456
+ console.console.print(
457
+ f'[error]Failed searching for testcase [item]{testcase_entry}[/item].[/error]'
458
+ )
459
+ raise typer.Exit(1)
460
+ generation_metadata = extracted[0].metadata
461
+ # Replace destination with the irun testcase we're using.
462
+ generation_metadata.copied_to = testcase
463
+ else:
464
+ with utils.no_progress(progress):
465
+ input = console.multiline_prompt('Testcase input')
466
+ testcase.inputPath.write_text(input)
467
+ console.console.print()
468
+
469
+ if (
470
+ testcase.outputPath is not None
471
+ and not testcase.outputPath.is_file()
472
+ and main_solution is None
473
+ ):
474
+ with utils.no_progress(progress):
475
+ output = console.multiline_prompt('Testcase output')
476
+ testcase.outputPath.write_text(output)
477
+ console.console.print()
478
+
479
+ generation_metadata = GenerationMetadata(
480
+ copied_to=testcase,
481
+ )
482
+ is_manual = True
483
+
484
+ # 1. Generate testcase.
485
+ if generation_metadata is not None:
486
+ generate_standalone(
487
+ generation_metadata,
488
+ progress=progress,
489
+ validate=True,
490
+ )
491
+ if testcase_entry is not None:
492
+ console.console.print(
493
+ f'Using input from testcase [item]{testcase_entry}[/item].'
494
+ )
495
+ elif generation_metadata.generator_call is not None:
496
+ console.console.print(
497
+ f'Using input from generator call [item]{generation_metadata.generator_call.name} {generation_metadata.generator_call.args}[/item].'
498
+ )
499
+ if print and not is_manual:
500
+ console.console.print(testcase.inputPath.read_text())
501
+ else:
502
+ console.console.print(
503
+ f'Input was written to [item]{testcase.inputPath.resolve()}[/item]'
504
+ )
505
+ console.console.print()
506
+
507
+ # 2. Generate test output from reference
440
508
  main_solution_digest = None
441
- if check and main_solution is not None:
509
+ if check and not (
510
+ testcase.outputPath is not None and testcase.outputPath.is_file()
511
+ ):
512
+ if main_solution is None:
513
+ console.console.print(
514
+ '[error]Checking is enabled but no main solution or custom output was specified.[/error]'
515
+ )
516
+ raise typer.Exit(1)
517
+
518
+ if progress:
519
+ progress.update('Compiling main solution...')
442
520
  try:
443
521
  main_solution_digest = compile_item(
444
522
  main_solution,
@@ -452,6 +530,42 @@ def _run_interactive_solutions(
452
530
  )
453
531
  raise
454
532
 
533
+ if main_solution_digest is not None:
534
+ if progress:
535
+ progress.update('Generating output for test...')
536
+ # TODO: Add stderr path
537
+ generate_output_for_testcase(main_solution_digest, testcase)
538
+
539
+ if check and testcase.outputPath is not None and not testcase.outputPath.is_file():
540
+ # Output was not created, throw an error.
541
+ console.console.print(
542
+ '[error]Checking is enabled but no output could be generated for this testcase.[/error]'
543
+ )
544
+ console.console.print(
545
+ '[error]Either specify it explicitly or provide a main solution.[/error]'
546
+ )
547
+ raise typer.Exit(1)
548
+
549
+ return testcase
550
+
551
+
552
+ def _run_interactive_solutions(
553
+ testcase: Testcase,
554
+ progress: Optional[StatusProgress] = None,
555
+ tracked_solutions: Optional[Set[str]] = None,
556
+ verification: VerificationLevel = VerificationLevel.NONE,
557
+ check: bool = True,
558
+ sanitized: bool = False,
559
+ ) -> Iterator[EvaluationItem]:
560
+ pkg = package.find_problem_package_or_die()
561
+
562
+ if check and progress:
563
+ progress.update('Compiling checker...')
564
+ checker_digest = checkers.compile_checker() if check else None
565
+ compiled_solutions = compile_solutions(
566
+ progress=progress, tracked_solutions=tracked_solutions, sanitized=sanitized
567
+ )
568
+
455
569
  solutions = list(enumerate(pkg.solutions))
456
570
  if tracked_solutions is not None:
457
571
  solutions = [
@@ -459,33 +573,9 @@ def _run_interactive_solutions(
459
573
  ]
460
574
 
461
575
  irun_dir = package.get_problem_iruns_dir()
462
- shutil.rmtree(str(irun_dir), ignore_errors=True)
463
- irun_dir.mkdir(parents=True, exist_ok=True)
464
- inputs_dir = irun_dir / 'inputs'
465
- inputs_dir.mkdir(parents=True, exist_ok=True)
466
- input_path = inputs_dir / '000.in'
467
- output_path = input_path.with_suffix('.out')
468
576
 
469
- if generator is not None:
470
- expanded_call = generate_standalone(generator, input_path)
471
- console.console.print(
472
- f'Using input from generator call [item]{expanded_call.name} {expanded_call.args}[/item].'
473
- )
474
- if print:
475
- console.console.print(input_path.read_text())
476
- else:
477
- console.console.print(
478
- f'Input was written to [item]{input_path.resolve()}[/item]'
479
- )
480
- console.console.print()
481
- else:
482
- input = console.multiline_prompt('Testcase input')
483
- input_path.write_text(input)
484
- testcase = Testcase(inputPath=input_path, outputPath=output_path if check else None)
485
-
486
- if main_solution_digest is not None:
487
- # TODO: Add stderr path
488
- generate_output_for_testcase(main_solution_digest, testcase)
577
+ if progress:
578
+ progress.update('Running solutions...')
489
579
 
490
580
  for i, solution in solutions:
491
581
  output_dir = irun_dir / f'{i}'
@@ -513,27 +603,38 @@ async def run_and_print_interactive_solutions(
513
603
  tracked_solutions: Optional[Set[str]] = None,
514
604
  verification: VerificationLevel = VerificationLevel.NONE,
515
605
  generator: Optional[GeneratorCall] = None,
606
+ testcase_entry: Optional[TestcaseEntry] = None,
516
607
  check: bool = True,
517
608
  print: bool = False,
518
609
  sanitized: bool = False,
519
610
  ):
611
+ # Ensure path is new.
612
+ irun_dir = package.get_problem_iruns_dir()
613
+ shutil.rmtree(str(irun_dir), ignore_errors=True)
614
+ irun_dir.mkdir(parents=True, exist_ok=True)
615
+
520
616
  pkg = package.find_problem_package_or_die()
617
+ testcase = await _generate_testcase_interactively(
618
+ progress=progress,
619
+ generator=generator,
620
+ testcase_entry=testcase_entry,
621
+ check=check,
622
+ sanitized=sanitized,
623
+ print=print,
624
+ )
521
625
  items = _run_interactive_solutions(
626
+ testcase,
522
627
  progress=progress,
523
628
  tracked_solutions=tracked_solutions,
524
629
  verification=verification,
525
630
  check=check,
526
- generator=generator,
527
631
  sanitized=sanitized,
528
- print=print,
529
632
  )
530
633
 
531
- if progress:
532
- progress.stop()
533
-
534
634
  for item in items:
535
635
  sol = pkg.solutions[item.solution_index]
536
- _print_solution_header(sol, console.console, is_irun=True)
636
+ with utils.no_progress(progress):
637
+ _print_solution_header(sol, console.console, is_irun=True)
537
638
 
538
639
  eval = await item.eval()
539
640
 
@@ -557,7 +658,7 @@ async def run_and_print_interactive_solutions(
557
658
 
558
659
 
559
660
  def _get_solution_repr(sol: Solution) -> List[Tuple[str, str]]:
560
- fg_color = sol.outcome.style().replace('lnumber', 'cyan')
661
+ fg_color = sol.outcome.style()
561
662
  return [
562
663
  ('', f'{str(sol.path)} '),
563
664
  (f'fg:{fg_color}', sol.outcome.name),
@@ -590,9 +691,9 @@ def get_outcome_style_verdict(outcome: Outcome) -> str:
590
691
  if outcome == Outcome.TIME_LIMIT_EXCEEDED:
591
692
  return 'yellow'
592
693
  if outcome == Outcome.RUNTIME_ERROR:
593
- return 'lnumber'
694
+ return 'blue'
594
695
  if outcome == Outcome.MEMORY_LIMIT_EXCEEDED:
595
- return 'cyan'
696
+ return 'yellow'
596
697
  return 'magenta'
597
698
 
598
699
 
@@ -629,10 +730,6 @@ def _get_evals_memory_in_bytes(evals: List[Evaluation]) -> int:
629
730
  return max(int(eval.log.memory or 0) for eval in evals)
630
731
 
631
732
 
632
- def get_formatted_time(time_in_ms: int) -> str:
633
- return f'{time_in_ms} ms'
634
-
635
-
636
733
  def get_evals_formatted_time(evals: List[Evaluation]) -> str:
637
734
  max_time = _get_evals_time_in_ms(evals)
638
735
  return get_formatted_time(max_time)
@@ -665,14 +762,6 @@ def get_capped_evals_formatted_time(
665
762
  return f'{max_time} ms'
666
763
 
667
764
 
668
- def get_formatted_memory(memory_in_bytes: int) -> str:
669
- if memory_in_bytes < 1024 * 1024:
670
- if memory_in_bytes < 1024:
671
- return f'{memory_in_bytes} B'
672
- return f'{memory_in_bytes // 1024} KiB'
673
- return f'{memory_in_bytes // (1024 * 1024)} MiB'
674
-
675
-
676
765
  def get_evals_formatted_memory(evals: List[Evaluation]) -> str:
677
766
  max_memory = _get_evals_memory_in_bytes(evals)
678
767
  return get_formatted_memory(max_memory)
rbx/box/state.py ADDED
@@ -0,0 +1,9 @@
1
+ import dataclasses
2
+
3
+
4
+ @dataclasses.dataclass
5
+ class State:
6
+ run_through_cli: bool = False
7
+
8
+
9
+ STATE = State()