rbx.cp 0.13.7__py3-none-any.whl → 0.14.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 (72) hide show
  1. rbx/box/cli.py +74 -70
  2. rbx/box/code.py +3 -0
  3. rbx/box/contest/build_contest_statements.py +65 -23
  4. rbx/box/contest/contest_package.py +8 -1
  5. rbx/box/contest/main.py +9 -3
  6. rbx/box/contest/schema.py +17 -13
  7. rbx/box/contest/statements.py +12 -8
  8. rbx/box/dump_schemas.py +2 -1
  9. rbx/box/environment.py +1 -1
  10. rbx/box/fields.py +22 -4
  11. rbx/box/generators.py +32 -13
  12. rbx/box/limits_info.py +161 -0
  13. rbx/box/package.py +18 -1
  14. rbx/box/packaging/boca/boca_language_utils.py +26 -0
  15. rbx/box/packaging/boca/boca_outcome_utils.py +10 -0
  16. rbx/box/packaging/boca/packager.py +7 -5
  17. rbx/box/packaging/contest_main.py +20 -12
  18. rbx/box/packaging/packager.py +24 -14
  19. rbx/box/packaging/polygon/packager.py +7 -3
  20. rbx/box/packaging/polygon/upload.py +9 -2
  21. rbx/box/presets/__init__.py +64 -64
  22. rbx/box/remote.py +3 -3
  23. rbx/box/sanitizers/issue_stack.py +124 -0
  24. rbx/box/schema.py +87 -27
  25. rbx/box/solutions.py +74 -117
  26. rbx/box/statements/build_statements.py +12 -1
  27. rbx/box/statements/builders.py +5 -3
  28. rbx/box/statements/latex_jinja.py +73 -23
  29. rbx/box/statements/schema.py +7 -9
  30. rbx/box/stressing/generator_parser.py +3 -1
  31. rbx/box/tasks.py +10 -10
  32. rbx/box/testcase_extractors.py +8 -0
  33. rbx/box/testcase_utils.py +7 -7
  34. rbx/box/testing/testing_preset.py +129 -2
  35. rbx/box/testing/testing_shared.py +3 -1
  36. rbx/box/timing.py +305 -0
  37. rbx/box/tooling/boca/debug_utils.py +88 -0
  38. rbx/box/tooling/boca/manual_scrape.py +20 -0
  39. rbx/box/tooling/boca/scraper.py +660 -57
  40. rbx/box/unit.py +0 -2
  41. rbx/box/validators.py +0 -4
  42. rbx/grading/judge/cacher.py +36 -0
  43. rbx/grading/judge/program.py +12 -2
  44. rbx/grading/judge/sandbox.py +1 -1
  45. rbx/grading/judge/sandboxes/stupid_sandbox.py +2 -1
  46. rbx/grading/judge/storage.py +36 -3
  47. rbx/grading/limits.py +4 -0
  48. rbx/grading/steps.py +3 -2
  49. rbx/resources/presets/default/contest/contest.rbx.yml +11 -1
  50. rbx/resources/presets/default/contest/statement/info.rbx.tex +54 -0
  51. rbx/resources/presets/default/problem/.gitignore +1 -0
  52. rbx/resources/presets/default/problem/problem.rbx.yml +21 -3
  53. rbx/resources/presets/default/problem/rbx.h +52 -5
  54. rbx/resources/presets/default/problem/statement/statement.rbx.tex +6 -2
  55. rbx/resources/presets/default/problem/testlib.h +6299 -0
  56. rbx/resources/presets/default/problem/validator.cpp +4 -3
  57. rbx/resources/presets/default/shared/contest_template.rbx.tex +13 -3
  58. rbx/resources/presets/default/shared/icpc.sty +33 -5
  59. rbx/resources/presets/default/shared/problem_template.rbx.tex +10 -1
  60. rbx/testing_utils.py +17 -1
  61. {rbx_cp-0.13.7.dist-info → rbx_cp-0.14.0.dist-info}/METADATA +4 -2
  62. {rbx_cp-0.13.7.dist-info → rbx_cp-0.14.0.dist-info}/RECORD +66 -63
  63. {rbx_cp-0.13.7.dist-info → rbx_cp-0.14.0.dist-info}/WHEEL +1 -1
  64. {rbx_cp-0.13.7.dist-info → rbx_cp-0.14.0.dist-info}/entry_points.txt +0 -1
  65. rbx/providers/__init__.py +0 -43
  66. rbx/providers/codeforces.py +0 -73
  67. rbx/providers/provider.py +0 -26
  68. rbx/submitors/__init__.py +0 -18
  69. rbx/submitors/codeforces.py +0 -121
  70. rbx/submitors/submitor.py +0 -25
  71. /rbx/resources/presets/default/problem/sols/{wa.cpp → wa-overflow.cpp} +0 -0
  72. {rbx_cp-0.13.7.dist-info → rbx_cp-0.14.0.dist-info}/LICENSE +0 -0
@@ -20,7 +20,7 @@ app = typer.Typer(no_args_is_help=True)
20
20
  _FALLBACK_PRESET_URI = 'rsalesc/rbx/rbx/resources/presets/default'
21
21
 
22
22
 
23
- def _find_preset_yaml(root: pathlib.Path = pathlib.Path()) -> Optional[pathlib.Path]:
23
+ def find_preset_yaml(root: pathlib.Path = pathlib.Path()) -> Optional[pathlib.Path]:
24
24
  found = root / 'preset.rbx.yml'
25
25
  if found.exists():
26
26
  return found
@@ -28,7 +28,7 @@ def _find_preset_yaml(root: pathlib.Path = pathlib.Path()) -> Optional[pathlib.P
28
28
 
29
29
 
30
30
  def get_preset_yaml(root: pathlib.Path = pathlib.Path()) -> Preset:
31
- found = _find_preset_yaml(root)
31
+ found = find_preset_yaml(root)
32
32
  if not found:
33
33
  console.console.print(
34
34
  f'[error][item]preset.rbx.yml[/item] not found in [item]{root.absolute()}[/item][/error]'
@@ -45,14 +45,14 @@ def _find_preset_lock(root: pathlib.Path = pathlib.Path()) -> Optional[pathlib.P
45
45
  return problem_yaml_path
46
46
 
47
47
 
48
- def _get_preset_lock(root: pathlib.Path = pathlib.Path()) -> Optional[PresetLock]:
48
+ def get_preset_lock(root: pathlib.Path = pathlib.Path()) -> Optional[PresetLock]:
49
49
  found = _find_preset_lock(root)
50
50
  if not found:
51
51
  return None
52
52
  return utils.model_from_yaml(PresetLock, found.read_text())
53
53
 
54
54
 
55
- def _find_nested_preset(root: pathlib.Path) -> Optional[pathlib.Path]:
55
+ def find_nested_preset(root: pathlib.Path) -> Optional[pathlib.Path]:
56
56
  root = utils.abspath(root)
57
57
  problem_yaml_path = root / 'preset.rbx.yml'
58
58
  while root != pathlib.PosixPath('/') and not problem_yaml_path.is_file():
@@ -63,7 +63,7 @@ def _find_nested_preset(root: pathlib.Path) -> Optional[pathlib.Path]:
63
63
  return problem_yaml_path.parent
64
64
 
65
65
 
66
- def _find_local_preset(root: pathlib.Path) -> Optional[pathlib.Path]:
66
+ def find_local_preset(root: pathlib.Path) -> Optional[pathlib.Path]:
67
67
  original_root = root
68
68
  root = utils.abspath(root)
69
69
  problem_yaml_path = root / '.local.rbx' / 'preset.rbx.yml'
@@ -71,12 +71,12 @@ def _find_local_preset(root: pathlib.Path) -> Optional[pathlib.Path]:
71
71
  root = root.parent
72
72
  problem_yaml_path = root / '.local.rbx' / 'preset.rbx.yml'
73
73
  if not problem_yaml_path.is_file():
74
- return _find_nested_preset(original_root)
74
+ return find_nested_preset(original_root)
75
75
  return problem_yaml_path.parent
76
76
 
77
77
 
78
78
  def _is_installed_preset(root: pathlib.Path = pathlib.Path()) -> bool:
79
- preset_path = _find_local_preset(root)
79
+ preset_path = find_local_preset(root)
80
80
  if preset_path is None:
81
81
  return False
82
82
  resolved_path = utils.abspath(preset_path)
@@ -84,25 +84,25 @@ def _is_installed_preset(root: pathlib.Path = pathlib.Path()) -> bool:
84
84
 
85
85
 
86
86
  def _is_active_preset_nested(root: pathlib.Path = pathlib.Path()) -> bool:
87
- preset_path = _find_local_preset(root)
87
+ preset_path = find_local_preset(root)
88
88
  if preset_path is None:
89
89
  return False
90
- nested_preset_path = _find_nested_preset(root)
90
+ nested_preset_path = find_nested_preset(root)
91
91
  if nested_preset_path is None:
92
92
  return False
93
93
  return nested_preset_path == preset_path
94
94
 
95
95
 
96
- def _is_contest(root: pathlib.Path = pathlib.Path()) -> bool:
96
+ def is_contest(root: pathlib.Path = pathlib.Path()) -> bool:
97
97
  return (root / 'contest.rbx.yml').is_file()
98
98
 
99
99
 
100
- def _is_problem(root: pathlib.Path = pathlib.Path()) -> bool:
100
+ def is_problem(root: pathlib.Path = pathlib.Path()) -> bool:
101
101
  return (root / 'problem.rbx.yml').is_file()
102
102
 
103
103
 
104
- def _check_is_valid_package(root: pathlib.Path = pathlib.Path()):
105
- if not _is_contest(root) and not _is_problem(root):
104
+ def check_is_valid_package(root: pathlib.Path = pathlib.Path()):
105
+ if not is_contest(root) and not is_problem(root):
106
106
  console.console.print('[error]Not a valid rbx package directory.[/error]')
107
107
  raise typer.Exit(1)
108
108
 
@@ -110,7 +110,7 @@ def _check_is_valid_package(root: pathlib.Path = pathlib.Path()):
110
110
  def _glob_while_ignoring(
111
111
  dir: pathlib.Path,
112
112
  glb: str,
113
- extra_gitignore: Optional[str] = '.box\nbuild\n',
113
+ extra_gitignore: Optional[str] = '.box\nbuild\n.limits/local.yml\n',
114
114
  recursive: bool = False,
115
115
  ) -> Iterable[pathlib.Path]:
116
116
  from gitignore_parser import parse_gitignore, parse_gitignore_str
@@ -133,7 +133,7 @@ def _glob_while_ignoring(
133
133
  yield file
134
134
 
135
135
 
136
- def _process_globbing(
136
+ def process_globbing(
137
137
  assets: Iterable[TrackedAsset], preset_pkg_dir: pathlib.Path
138
138
  ) -> List[TrackedAsset]:
139
139
  res = []
@@ -156,7 +156,7 @@ def _process_globbing(
156
156
  return res
157
157
 
158
158
 
159
- def _dedup_tracked_assets(assets: List[TrackedAsset]) -> List[TrackedAsset]:
159
+ def dedup_tracked_assets(assets: List[TrackedAsset]) -> List[TrackedAsset]:
160
160
  seen_paths = set()
161
161
  res = []
162
162
  for asset in assets:
@@ -167,11 +167,11 @@ def _dedup_tracked_assets(assets: List[TrackedAsset]) -> List[TrackedAsset]:
167
167
  return res
168
168
 
169
169
 
170
- def _get_preset_tracked_assets(
170
+ def get_preset_tracked_assets(
171
171
  root: pathlib.Path, is_contest: bool, add_symlinks: bool = False
172
172
  ) -> List[TrackedAsset]:
173
173
  preset = get_active_preset(root)
174
- preset_path = _find_local_preset(root)
174
+ preset_path = find_local_preset(root)
175
175
  assert preset_path is not None
176
176
 
177
177
  if is_contest:
@@ -179,13 +179,13 @@ def _get_preset_tracked_assets(
179
179
  preset.contest is not None
180
180
  ), 'Preset does not have a contest package definition.'
181
181
  preset_pkg_path = preset_path / preset.contest
182
- res = _process_globbing(preset.tracking.contest, preset_pkg_path)
182
+ res = process_globbing(preset.tracking.contest, preset_pkg_path)
183
183
  else:
184
184
  assert (
185
185
  preset.problem is not None
186
186
  ), 'Preset does not have a problem package definition,'
187
187
  preset_pkg_path = preset_path / preset.problem
188
- res = _process_globbing(preset.tracking.problem, preset_pkg_path)
188
+ res = process_globbing(preset.tracking.problem, preset_pkg_path)
189
189
 
190
190
  if add_symlinks:
191
191
  for file in _glob_while_ignoring(
@@ -199,7 +199,7 @@ def _get_preset_tracked_assets(
199
199
  TrackedAsset(path=file.relative_to(preset_pkg_path), symlink=True)
200
200
  )
201
201
 
202
- return _dedup_tracked_assets(res)
202
+ return dedup_tracked_assets(res)
203
203
 
204
204
 
205
205
  def _get_tracked_assets_symlinks(
@@ -212,7 +212,7 @@ def _get_tracked_assets_symlinks(
212
212
  return res
213
213
 
214
214
 
215
- def _get_symlink_info(
215
+ def get_symlink_info(
216
216
  tracked_asset: Union[TrackedAsset, LockedAsset], root: pathlib.Path
217
217
  ) -> Optional[SymlinkInfo]:
218
218
  asset_path = root / tracked_asset.path
@@ -225,7 +225,7 @@ def _get_symlink_info(
225
225
  return SymlinkInfo(target=target, is_broken=is_broken, is_outside=is_outside)
226
226
 
227
227
 
228
- def _build_package_locked_assets(
228
+ def build_package_locked_assets(
229
229
  tracked_assets: Sequence[Union[TrackedAsset, LockedAsset]],
230
230
  root: pathlib.Path = pathlib.Path(),
231
231
  ) -> List[LockedAsset]:
@@ -237,7 +237,7 @@ def _build_package_locked_assets(
237
237
  LockedAsset(
238
238
  path=tracked_asset.path,
239
239
  hash=None,
240
- symlink_info=_get_symlink_info(tracked_asset, root),
240
+ symlink_info=get_symlink_info(tracked_asset, root),
241
241
  )
242
242
  )
243
243
  continue
@@ -246,13 +246,13 @@ def _build_package_locked_assets(
246
246
  LockedAsset(
247
247
  path=tracked_asset.path,
248
248
  hash=digest_cooperatively(f),
249
- symlink_info=_get_symlink_info(tracked_asset, root),
249
+ symlink_info=get_symlink_info(tracked_asset, root),
250
250
  )
251
251
  )
252
252
  return res
253
253
 
254
254
 
255
- def _find_non_modified_assets(
255
+ def find_non_modified_assets(
256
256
  reference: List[LockedAsset], current: List[LockedAsset]
257
257
  ) -> List[LockedAsset]:
258
258
  reference_by_path = {asset.path: asset for asset in reference}
@@ -273,7 +273,7 @@ def _find_non_modified_assets(
273
273
  return res
274
274
 
275
275
 
276
- def _find_modified_assets(
276
+ def find_modified_assets(
277
277
  reference: List[LockedAsset],
278
278
  current: List[LockedAsset],
279
279
  seen_symlinks: Set[pathlib.Path],
@@ -297,7 +297,7 @@ def _find_modified_assets(
297
297
  return res
298
298
 
299
299
 
300
- def _copy_preset_file(
300
+ def copy_preset_file(
301
301
  src: pathlib.Path,
302
302
  dst: pathlib.Path,
303
303
  preset_package_path: pathlib.Path,
@@ -358,19 +358,19 @@ def _copy_updated_assets(
358
358
  preset_path = get_active_preset_path(root)
359
359
  preset_package_path = _get_active_preset_package_path(root, is_contest)
360
360
 
361
- preset_tracked_assets = _get_preset_tracked_assets(
361
+ preset_tracked_assets = get_preset_tracked_assets(
362
362
  preset_package_path, is_contest=is_contest, add_symlinks=symlinks
363
363
  )
364
- current_preset_snapshot = _build_package_locked_assets(
364
+ current_preset_snapshot = build_package_locked_assets(
365
365
  preset_tracked_assets, preset_package_path
366
366
  )
367
367
 
368
368
  # Build current package snapshot based on the current preset snapshot.
369
- current_package_snapshot = _build_package_locked_assets(current_preset_snapshot)
369
+ current_package_snapshot = build_package_locked_assets(current_preset_snapshot)
370
370
 
371
371
  non_modified_assets = current_package_snapshot
372
372
  if not force:
373
- non_modified_assets = _find_non_modified_assets(
373
+ non_modified_assets = find_non_modified_assets(
374
374
  preset_lock.assets, current_package_snapshot
375
375
  )
376
376
 
@@ -386,7 +386,7 @@ def _copy_updated_assets(
386
386
 
387
387
  seen_symlinks = _get_tracked_assets_symlinks(preset_tracked_assets)
388
388
 
389
- assets_to_copy = _find_modified_assets(
389
+ assets_to_copy = find_modified_assets(
390
390
  non_modified_assets, current_preset_snapshot, seen_symlinks
391
391
  )
392
392
 
@@ -402,7 +402,7 @@ def _copy_updated_assets(
402
402
  for asset in assets_to_copy:
403
403
  src_path = preset_package_path / asset.path
404
404
  dst_path = root / asset.path
405
- _copy_preset_file(
405
+ copy_preset_file(
406
406
  src_path,
407
407
  dst_path,
408
408
  preset_package_path,
@@ -415,7 +415,7 @@ def _copy_updated_assets(
415
415
 
416
416
 
417
417
  def get_active_preset_or_null(root: pathlib.Path = pathlib.Path()) -> Optional[Preset]:
418
- local_preset = _find_local_preset(root)
418
+ local_preset = find_local_preset(root)
419
419
  if local_preset is not None:
420
420
  return get_preset_yaml(local_preset)
421
421
  return None
@@ -430,7 +430,7 @@ def get_active_preset(root: pathlib.Path = pathlib.Path()) -> Preset:
430
430
 
431
431
 
432
432
  def get_active_preset_path(root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
433
- preset_path = _find_local_preset(root)
433
+ preset_path = find_local_preset(root)
434
434
  if preset_path is None:
435
435
  console.console.print('[error]No preset is active.[/error]')
436
436
  raise typer.Exit(1)
@@ -458,7 +458,7 @@ def _get_active_preset_package_path(
458
458
  is_contest: bool = False,
459
459
  ) -> pathlib.Path:
460
460
  preset = get_active_preset(root)
461
- preset_path = _find_local_preset(root)
461
+ preset_path = find_local_preset(root)
462
462
  assert preset_path is not None
463
463
  if is_contest:
464
464
  assert (
@@ -488,26 +488,26 @@ def get_preset_fetch_info_with_fallback(
488
488
  return get_preset_fetch_info(uri)
489
489
 
490
490
 
491
- def _clean_copied_package_dir(dest: pathlib.Path):
491
+ def clean_copied_package_dir(dest: pathlib.Path):
492
492
  for box_dir in dest.rglob('.box'):
493
493
  shutil.rmtree(str(box_dir), ignore_errors=True)
494
494
  for lock in dest.rglob('.preset-lock.yml'):
495
495
  lock.unlink(missing_ok=True)
496
496
 
497
497
 
498
- def _clean_copied_contest_dir(dest: pathlib.Path, delete_local_rbx: bool = True):
498
+ def clean_copied_contest_dir(dest: pathlib.Path, delete_local_rbx: bool = True):
499
499
  shutil.rmtree(str(dest / 'build'), ignore_errors=True)
500
500
  if delete_local_rbx:
501
501
  shutil.rmtree(str(dest / '.local.rbx'), ignore_errors=True)
502
- _clean_copied_package_dir(dest)
502
+ clean_copied_package_dir(dest)
503
503
 
504
504
 
505
- def _clean_copied_problem_dir(dest: pathlib.Path):
505
+ def clean_copied_problem_dir(dest: pathlib.Path):
506
506
  shutil.rmtree(str(dest / 'build'), ignore_errors=True)
507
- _clean_copied_package_dir(dest)
507
+ clean_copied_package_dir(dest)
508
508
 
509
509
 
510
- def _install_preset_from_dir(
510
+ def install_preset_from_dir(
511
511
  src: pathlib.Path,
512
512
  dest: pathlib.Path,
513
513
  ensure_contest: bool = False,
@@ -541,11 +541,11 @@ def _install_preset_from_dir(
541
541
  shutil.rmtree(str(dest / '.local.rbx'), ignore_errors=True)
542
542
 
543
543
  if preset.contest is not None:
544
- _clean_copied_contest_dir(dest / preset.contest)
544
+ clean_copied_contest_dir(dest / preset.contest)
545
545
  if preset.problem is not None:
546
- _clean_copied_problem_dir(dest / preset.problem)
546
+ clean_copied_problem_dir(dest / preset.problem)
547
547
 
548
- _clean_copied_package_dir(dest)
548
+ clean_copied_package_dir(dest)
549
549
 
550
550
 
551
551
  def _install_preset_from_remote(
@@ -569,7 +569,7 @@ def _install_preset_from_remote(
569
569
  f'Installing preset from [item]{fetch_info.inner_dir}[/item].'
570
570
  )
571
571
  pd = pd / fetch_info.inner_dir
572
- _install_preset_from_dir(
572
+ install_preset_from_dir(
573
573
  pd,
574
574
  dest,
575
575
  ensure_contest,
@@ -591,7 +591,7 @@ def _install_preset_from_local_dir(
591
591
  console.console.print(
592
592
  f'Installing local preset [item]{preset.name}[/item] into [item]{dest}[/item]...'
593
593
  )
594
- _install_preset_from_dir(
594
+ install_preset_from_dir(
595
595
  pd,
596
596
  dest,
597
597
  ensure_contest,
@@ -617,7 +617,7 @@ def _install_preset_from_resources(
617
617
  console.console.print(
618
618
  f'Installing preset [item]{fetch_info.name}[/item] from resources...'
619
619
  )
620
- _install_preset_from_dir(
620
+ install_preset_from_dir(
621
621
  rsrc_preset_path,
622
622
  dest,
623
623
  ensure_contest,
@@ -692,7 +692,7 @@ def _install_package_from_preset(
692
692
  ):
693
693
  if not file.is_file():
694
694
  continue
695
- _copy_preset_file(
695
+ copy_preset_file(
696
696
  file,
697
697
  dest_pkg / file.relative_to(preset_package_path),
698
698
  preset_package_path,
@@ -702,7 +702,7 @@ def _install_package_from_preset(
702
702
  for asset in tracked_assets:
703
703
  if not asset.symlink:
704
704
  continue
705
- _copy_preset_file(
705
+ copy_preset_file(
706
706
  preset_package_path / asset.path,
707
707
  dest_pkg / asset.path,
708
708
  preset_package_path,
@@ -721,7 +721,7 @@ def install_contest(
721
721
  ensure_contest=True,
722
722
  )
723
723
  preset = get_active_preset(dest_pkg)
724
- preset_path = _find_local_preset(dest_pkg)
724
+ preset_path = find_local_preset(dest_pkg)
725
725
  assert preset_path is not None
726
726
  if preset.contest is None:
727
727
  console.console.print(
@@ -735,7 +735,7 @@ def install_contest(
735
735
  _install_package_from_preset(
736
736
  preset_path, preset.contest, dest_pkg, preset.tracking.contest
737
737
  )
738
- _clean_copied_contest_dir(dest_pkg, delete_local_rbx=False)
738
+ clean_copied_contest_dir(dest_pkg, delete_local_rbx=False)
739
739
 
740
740
 
741
741
  def install_problem(
@@ -748,7 +748,7 @@ def install_problem(
748
748
  ensure_problem=True,
749
749
  )
750
750
  preset = get_active_preset(dest_pkg)
751
- preset_path = _find_local_preset(dest_pkg)
751
+ preset_path = find_local_preset(dest_pkg)
752
752
  assert preset_path is not None
753
753
  if preset.problem is None:
754
754
  console.console.print(
@@ -762,7 +762,7 @@ def install_problem(
762
762
  _install_package_from_preset(
763
763
  preset_path, preset.problem, dest_pkg, preset.tracking.problem
764
764
  )
765
- _clean_copied_problem_dir(dest_pkg)
765
+ clean_copied_problem_dir(dest_pkg)
766
766
 
767
767
 
768
768
  def install_preset(
@@ -774,7 +774,7 @@ def install_preset(
774
774
  )
775
775
  raise typer.Exit(1)
776
776
  if fetch_info is None:
777
- _install_preset_from_dir(get_active_preset_path(), dest_pkg)
777
+ install_preset_from_dir(get_active_preset_path(), dest_pkg)
778
778
  else:
779
779
  _install_preset_from_fetch_info(fetch_info, dest_pkg)
780
780
 
@@ -792,10 +792,10 @@ def get_ruyaml(root: pathlib.Path = pathlib.Path()) -> Tuple[ruyaml.YAML, ruyaml
792
792
  def generate_lock(root: pathlib.Path = pathlib.Path()):
793
793
  preset = get_active_preset(root)
794
794
 
795
- tracked_assets = _get_preset_tracked_assets(root, is_contest=_is_contest(root))
795
+ tracked_assets = get_preset_tracked_assets(root, is_contest=is_contest(root))
796
796
  preset_lock = PresetLock(
797
797
  name=preset.name,
798
- assets=_build_package_locked_assets(tracked_assets, root),
798
+ assets=build_package_locked_assets(tracked_assets, root),
799
799
  )
800
800
 
801
801
  (root / '.preset-lock.yml').write_text(utils.model_to_yaml(preset_lock))
@@ -805,7 +805,7 @@ def generate_lock(root: pathlib.Path = pathlib.Path()):
805
805
 
806
806
 
807
807
  def _sync(try_update: bool = False, force: bool = False, symlinks: bool = False):
808
- preset_lock = _get_preset_lock()
808
+ preset_lock = get_preset_lock()
809
809
  if preset_lock is None:
810
810
  console.console.print(
811
811
  '[error]Package does not have a [item].preset.lock.yml[/item] file and thus cannot be synced.[/error]'
@@ -820,7 +820,7 @@ def _sync(try_update: bool = False, force: bool = False, symlinks: bool = False)
820
820
 
821
821
  _copy_updated_assets(
822
822
  preset_lock,
823
- is_contest=_is_contest(),
823
+ is_contest=is_contest(),
824
824
  force=force,
825
825
  symlinks=symlinks,
826
826
  )
@@ -969,7 +969,7 @@ def update():
969
969
  ).ask():
970
970
  return
971
971
 
972
- preset_path = _find_local_preset(pathlib.Path.cwd())
972
+ preset_path = find_local_preset(pathlib.Path.cwd())
973
973
  assert preset_path is not None
974
974
  _install_preset_from_fetch_info(preset.fetch_info, dest=preset_path, update=True)
975
975
  console.console.print(
@@ -1008,7 +1008,7 @@ def sync(
1008
1008
  ),
1009
1009
  ] = False,
1010
1010
  ):
1011
- _check_is_valid_package()
1011
+ check_is_valid_package()
1012
1012
  _sync(try_update=update, force=force, symlinks=symlinks)
1013
1013
 
1014
1014
 
@@ -1019,7 +1019,7 @@ def sync(
1019
1019
  )
1020
1020
  @cd.within_closest_package
1021
1021
  def lock():
1022
- _check_is_valid_package()
1022
+ check_is_valid_package()
1023
1023
  generate_lock()
1024
1024
 
1025
1025
 
@@ -1027,7 +1027,7 @@ def lock():
1027
1027
  @cd.within_closest_package
1028
1028
  def ls():
1029
1029
  preset = get_active_preset()
1030
- preset_path = _find_local_preset(pathlib.Path.cwd())
1030
+ preset_path = find_local_preset(pathlib.Path.cwd())
1031
1031
  console.console.print(f'Preset: [item]{preset.name}[/item]')
1032
1032
  console.console.print(f'Path: {preset_path}')
1033
1033
  console.console.print(f'URI: {preset.uri}')
rbx/box/remote.py CHANGED
@@ -8,6 +8,7 @@ import typer
8
8
  from rbx import console, utils
9
9
  from rbx.box import cd, package
10
10
  from rbx.box.formatting import href, ref
11
+ from rbx.box.tooling.boca.scraper import BocaRun
11
12
 
12
13
  PathLike = Union[str, pathlib.Path]
13
14
 
@@ -69,11 +70,10 @@ class BocaExpander(Expander):
69
70
  return None
70
71
  run_number, site_number = match
71
72
 
73
+ run = BocaRun.from_run_number(run_number, site_number)
72
74
  boca_uploader = boca_upload.get_boca_scraper()
73
75
  boca_uploader.login()
74
- sol_path = boca_uploader.download_run(
75
- run_number, site_number, self.get_boca_folder()
76
- )
76
+ sol_path = boca_uploader.download_run(run, self.get_boca_folder())
77
77
  console.console.print(f'Downloaded {href(sol_path)} from BOCA...')
78
78
  return sol_path
79
79
 
@@ -0,0 +1,124 @@
1
+ import contextvars
2
+ import enum
3
+ from collections import OrderedDict
4
+ from typing import Callable, Optional, Tuple, Union
5
+
6
+ from rbx import console
7
+
8
+
9
+ class IssueLevel(enum.Enum):
10
+ OVERVIEW = enum.auto()
11
+ DETAILED = enum.auto()
12
+
13
+
14
+ class Issue:
15
+ def get_detailed_section(self) -> Optional[Tuple[str, ...]]:
16
+ return None
17
+
18
+ def get_overview_section(self) -> Optional[Tuple[str, ...]]:
19
+ return None
20
+
21
+ def get_detailed_message(self) -> str:
22
+ return ''
23
+
24
+ def get_overview_message(self) -> str:
25
+ return ''
26
+
27
+
28
+ IssueSection = OrderedDict[str, Union[Issue, 'IssueSection']]
29
+
30
+
31
+ class IssueAccumulator:
32
+ def __init__(self):
33
+ self.issues = []
34
+
35
+ def add_issue(self, issue: Issue):
36
+ self.issues.append(issue)
37
+
38
+ def get_sections_by(
39
+ self, key: Callable[[Issue], Optional[Tuple[str, ...]]]
40
+ ) -> IssueSection:
41
+ sections = OrderedDict()
42
+ for issue in self.issues:
43
+ section_key = key(issue)
44
+ if section_key is None:
45
+ continue
46
+ current = sections
47
+ for k in section_key[:-1]:
48
+ current = current.setdefault(k, OrderedDict())
49
+ current[section_key[-1]] = issue
50
+ return sections
51
+
52
+ def get_detailed_sections(self) -> IssueSection:
53
+ return self.get_sections_by(lambda issue: issue.get_detailed_section())
54
+
55
+ def get_overview_sections(self) -> IssueSection:
56
+ return self.get_sections_by(lambda issue: issue.get_overview_section())
57
+
58
+ def _print_report_by(
59
+ self,
60
+ section_fn: Callable[[], IssueSection],
61
+ message_fn: Callable[[Issue], str],
62
+ ):
63
+ from rich.tree import Tree
64
+
65
+ tree = Tree('Issues')
66
+ sections = section_fn()
67
+
68
+ def print_section(section: IssueSection, tree: Tree):
69
+ for key, value in section.items():
70
+ child = tree.add(key)
71
+ if isinstance(value, OrderedDict):
72
+ print_section(value, child)
73
+ else:
74
+ child.add(f'[error]{message_fn(value)}[/error]')
75
+
76
+ print_section(sections, tree)
77
+
78
+ if tree.children:
79
+ console.console.rule('Issues', style='error')
80
+ for child in tree.children:
81
+ console.console.print(child)
82
+
83
+ def print_detailed_report(self):
84
+ self._print_report_by(
85
+ self.get_detailed_sections, lambda issue: issue.get_detailed_message()
86
+ )
87
+
88
+ def print_overview_report(self):
89
+ self._print_report_by(
90
+ self.get_overview_sections, lambda issue: issue.get_overview_message()
91
+ )
92
+
93
+
94
+ issue_stack_var = contextvars.ContextVar('issue_stack', default=[IssueAccumulator()])
95
+ issue_level_var = contextvars.ContextVar('issue_level', default=IssueLevel.DETAILED)
96
+
97
+
98
+ def get_issue_stack() -> list[IssueAccumulator]:
99
+ return issue_stack_var.get()
100
+
101
+
102
+ def push_issue_accumulator():
103
+ issue_stack_var.set(get_issue_stack() + [IssueAccumulator()])
104
+
105
+
106
+ def pop_issue_accumulator():
107
+ issue_stack_var.set(get_issue_stack()[:-1])
108
+
109
+
110
+ def get_issue_accumulator() -> IssueAccumulator:
111
+ return get_issue_stack()[-1]
112
+
113
+
114
+ def add_issue(issue: Issue):
115
+ for acc in get_issue_stack():
116
+ acc.add_issue(issue)
117
+
118
+
119
+ def print_current_report():
120
+ acc = get_issue_accumulator()
121
+ if issue_level_var.get() == IssueLevel.OVERVIEW:
122
+ acc.print_overview_report()
123
+ else:
124
+ acc.print_detailed_report()