rbx.cp 0.5.25__py3-none-any.whl → 0.5.27__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/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
- contest['problems'].append(
151
- {
152
- 'short_name': short_name,
153
- 'path': path,
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
@@ -197,6 +197,12 @@ def run(
197
197
  if solution:
198
198
  tracked_solutions = {solution}
199
199
 
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)
205
+
200
206
  if sanitized and tracked_solutions is None:
201
207
  console.console.print(
202
208
  '[warning]Sanitizers are running, and no solutions were specified to run. Will only run [item]ACCEPTED[/item] solutions.'
@@ -206,12 +212,6 @@ def run(
206
212
  for solution in get_exact_matching_solutions(ExpectedOutcome.ACCEPTED)
207
213
  }
208
214
 
209
- if choice:
210
- tracked_solutions = set(pick_solutions(tracked_solutions))
211
- if not tracked_solutions:
212
- console.console.print('[error]No solutions selected. Exiting.[/error]')
213
- raise typer.Exit(1)
214
-
215
215
  with utils.StatusProgress('Running solutions...') as s:
216
216
  solution_result = run_solutions(
217
217
  progress=s,
@@ -385,6 +385,13 @@ def irun(
385
385
  }
386
386
  if solution:
387
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
+
388
395
  if sanitized and tracked_solutions is None:
389
396
  console.console.print(
390
397
  '[warning]Sanitizers are running, and no solutions were specified to run. Will only run [item]ACCEPTED[/item] solutions.'
@@ -394,12 +401,6 @@ def irun(
394
401
  for solution in get_exact_matching_solutions(ExpectedOutcome.ACCEPTED)
395
402
  }
396
403
 
397
- if choice:
398
- tracked_solutions = set(pick_solutions(tracked_solutions))
399
- if not tracked_solutions:
400
- console.console.print('[error]No solutions selected. Exiting.[/error]')
401
- raise typer.Exit(1)
402
-
403
404
  with utils.StatusProgress('Running solutions...') as s:
404
405
  asyncio.run(
405
406
  run_and_print_interactive_solutions(
@@ -539,6 +540,8 @@ def stress(
539
540
  name=testgroup, generatorScript=CodeItem(path=new_script_path)
540
541
  )
541
542
  ru, problem_yml = package.get_ruyaml()
543
+ if 'testcases' not in problem_yml:
544
+ problem_yml['testcases'] = []
542
545
  problem_yml['testcases'].append(
543
546
  {
544
547
  'name': testgroup,
@@ -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(root)
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(update: bool = False):
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 = update and preset_lock.uri is not None
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 update or preset_lock.uri is None:
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
- should_update = True
435
-
436
- if should_update:
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(update=update)
537
+ _sync(try_update=update)
511
538
 
512
539
 
513
540
  @app.command(
rbx/box/solutions.py CHANGED
@@ -13,6 +13,7 @@ import rich.live
13
13
  import rich.markup
14
14
  import rich.table
15
15
  import rich.text
16
+ import typer
16
17
  from pydantic import BaseModel
17
18
 
18
19
  from rbx import console
@@ -547,14 +548,30 @@ async def run_and_print_interactive_solutions(
547
548
  console.console.print()
548
549
 
549
550
 
551
+ def _get_solution_repr(sol: Solution) -> List[Tuple[str, str]]:
552
+ fg_color = sol.outcome.style().replace('lnumber', 'cyan')
553
+ return [
554
+ ('', f'{str(sol.path)} '),
555
+ (f'fg:{fg_color}', sol.outcome.name),
556
+ ]
557
+
558
+
550
559
  def pick_solutions(tracked_solutions: Optional[Set[str]]) -> List[str]:
551
560
  pkg = package.find_problem_package_or_die()
552
561
  if tracked_solutions is None:
553
562
  tracked_solutions = set(str(sol.path) for sol in pkg.solutions)
554
563
 
555
- return questionary.checkbox(
556
- 'Select solutions', choices=list(tracked_solutions)
557
- ).ask()
564
+ # Store in a separate list to maintain order with the package declaration.
565
+ choices = [
566
+ questionary.Choice(title=_get_solution_repr(sol), value=str(sol.path))
567
+ for sol in pkg.solutions
568
+ if str(sol.path) in tracked_solutions
569
+ ]
570
+
571
+ picked = questionary.checkbox('Select solutions', choices=choices).ask()
572
+ if picked is None:
573
+ raise typer.Abort()
574
+ return picked
558
575
 
559
576
 
560
577
  def get_outcome_style_verdict(outcome: Outcome) -> str:
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))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: rbx.cp
3
- Version: 0.5.25
3
+ Version: 0.5.27
4
4
  Summary:
5
5
  Author: Roberto Sales
6
6
  Requires-Python: >=3.9,<4.0
@@ -12,7 +12,7 @@ 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=S2UuDKOAgXfPLW_5MkkxpSADx0yMofpK1ud_Sw2foks,7328
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=MPMIyd8IzuwMJX1XSPESp1Ky33WCtxVmyhtHzSJQtAU,23199
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=Wiegp1onXPaZs8RE1J3PKT5j3PFWKw2U2rkgOSbnYeM,17529
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=nnirg9ifs2EdyhXVhSZ0rUhobZMdRF-WdYfLbdNyTwI,37523
42
+ rbx/box/solutions.py,sha256=KnF8f_bPGPvYBH_jdud9obziDIO8jEMOjjEHYrLIoT4,38065
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=Vro6PytOsaenEnufR40NcY2k5OHyyrEDEcMT3gypBgw,8388
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=l0iulQH3_jQEm455W66TbDtC4a8owkWTHIIQpJaXofQ,715
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
@@ -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.25.dist-info/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
166
- rbx_cp-0.5.25.dist-info/METADATA,sha256=EVWrYcC_J1TlCXE7aXFyRLTbcezWHkauiwvrva1kF_o,3290
167
- rbx_cp-0.5.25.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
168
- rbx_cp-0.5.25.dist-info/entry_points.txt,sha256=qBTLBOeifT1F00LWaEewRRE_jQPgvH7BUdJfZ-dYsFU,57
169
- rbx_cp-0.5.25.dist-info/RECORD,,
165
+ rbx_cp-0.5.27.dist-info/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
166
+ rbx_cp-0.5.27.dist-info/METADATA,sha256=8jFwdPKGFjf2KdKy6qhCDaRf0UJvVlG9e140Lz9ZDuc,3290
167
+ rbx_cp-0.5.27.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
168
+ rbx_cp-0.5.27.dist-info/entry_points.txt,sha256=qBTLBOeifT1F00LWaEewRRE_jQPgvH7BUdJfZ-dYsFU,57
169
+ rbx_cp-0.5.27.dist-info/RECORD,,