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.
- rbx/box/cli.py +74 -70
- rbx/box/code.py +3 -0
- rbx/box/contest/build_contest_statements.py +65 -23
- rbx/box/contest/contest_package.py +8 -1
- rbx/box/contest/main.py +9 -3
- rbx/box/contest/schema.py +17 -13
- rbx/box/contest/statements.py +12 -8
- rbx/box/dump_schemas.py +2 -1
- rbx/box/environment.py +1 -1
- rbx/box/fields.py +22 -4
- rbx/box/generators.py +32 -13
- rbx/box/limits_info.py +161 -0
- rbx/box/package.py +18 -1
- rbx/box/packaging/boca/boca_language_utils.py +26 -0
- rbx/box/packaging/boca/boca_outcome_utils.py +10 -0
- rbx/box/packaging/boca/packager.py +7 -5
- rbx/box/packaging/contest_main.py +20 -12
- rbx/box/packaging/packager.py +24 -14
- rbx/box/packaging/polygon/packager.py +7 -3
- rbx/box/packaging/polygon/upload.py +9 -2
- rbx/box/presets/__init__.py +64 -64
- rbx/box/remote.py +3 -3
- rbx/box/sanitizers/issue_stack.py +124 -0
- rbx/box/schema.py +87 -27
- rbx/box/solutions.py +74 -117
- rbx/box/statements/build_statements.py +12 -1
- rbx/box/statements/builders.py +5 -3
- rbx/box/statements/latex_jinja.py +73 -23
- rbx/box/statements/schema.py +7 -9
- rbx/box/stressing/generator_parser.py +3 -1
- rbx/box/tasks.py +10 -10
- rbx/box/testcase_extractors.py +8 -0
- rbx/box/testcase_utils.py +7 -7
- rbx/box/testing/testing_preset.py +129 -2
- rbx/box/testing/testing_shared.py +3 -1
- rbx/box/timing.py +305 -0
- rbx/box/tooling/boca/debug_utils.py +88 -0
- rbx/box/tooling/boca/manual_scrape.py +20 -0
- rbx/box/tooling/boca/scraper.py +660 -57
- rbx/box/unit.py +0 -2
- rbx/box/validators.py +0 -4
- rbx/grading/judge/cacher.py +36 -0
- rbx/grading/judge/program.py +12 -2
- rbx/grading/judge/sandbox.py +1 -1
- rbx/grading/judge/sandboxes/stupid_sandbox.py +2 -1
- rbx/grading/judge/storage.py +36 -3
- rbx/grading/limits.py +4 -0
- rbx/grading/steps.py +3 -2
- rbx/resources/presets/default/contest/contest.rbx.yml +11 -1
- rbx/resources/presets/default/contest/statement/info.rbx.tex +54 -0
- rbx/resources/presets/default/problem/.gitignore +1 -0
- rbx/resources/presets/default/problem/problem.rbx.yml +21 -3
- rbx/resources/presets/default/problem/rbx.h +52 -5
- rbx/resources/presets/default/problem/statement/statement.rbx.tex +6 -2
- rbx/resources/presets/default/problem/testlib.h +6299 -0
- rbx/resources/presets/default/problem/validator.cpp +4 -3
- rbx/resources/presets/default/shared/contest_template.rbx.tex +13 -3
- rbx/resources/presets/default/shared/icpc.sty +33 -5
- rbx/resources/presets/default/shared/problem_template.rbx.tex +10 -1
- rbx/testing_utils.py +17 -1
- {rbx_cp-0.13.7.dist-info → rbx_cp-0.14.0.dist-info}/METADATA +4 -2
- {rbx_cp-0.13.7.dist-info → rbx_cp-0.14.0.dist-info}/RECORD +66 -63
- {rbx_cp-0.13.7.dist-info → rbx_cp-0.14.0.dist-info}/WHEEL +1 -1
- {rbx_cp-0.13.7.dist-info → rbx_cp-0.14.0.dist-info}/entry_points.txt +0 -1
- rbx/providers/__init__.py +0 -43
- rbx/providers/codeforces.py +0 -73
- rbx/providers/provider.py +0 -26
- rbx/submitors/__init__.py +0 -18
- rbx/submitors/codeforces.py +0 -121
- rbx/submitors/submitor.py +0 -25
- /rbx/resources/presets/default/problem/sols/{wa.cpp → wa-overflow.cpp} +0 -0
- {rbx_cp-0.13.7.dist-info → rbx_cp-0.14.0.dist-info}/LICENSE +0 -0
rbx/box/presets/__init__.py
CHANGED
@@ -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
|
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 =
|
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
|
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
|
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
|
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
|
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 =
|
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 =
|
87
|
+
preset_path = find_local_preset(root)
|
88
88
|
if preset_path is None:
|
89
89
|
return False
|
90
|
-
nested_preset_path =
|
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
|
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
|
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
|
105
|
-
if not
|
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
|
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
|
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
|
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 =
|
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 =
|
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 =
|
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
|
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
|
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
|
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=
|
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=
|
249
|
+
symlink_info=get_symlink_info(tracked_asset, root),
|
250
250
|
)
|
251
251
|
)
|
252
252
|
return res
|
253
253
|
|
254
254
|
|
255
|
-
def
|
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
|
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
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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
|
-
|
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 =
|
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 =
|
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 =
|
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
|
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
|
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
|
-
|
502
|
+
clean_copied_package_dir(dest)
|
503
503
|
|
504
504
|
|
505
|
-
def
|
505
|
+
def clean_copied_problem_dir(dest: pathlib.Path):
|
506
506
|
shutil.rmtree(str(dest / 'build'), ignore_errors=True)
|
507
|
-
|
507
|
+
clean_copied_package_dir(dest)
|
508
508
|
|
509
509
|
|
510
|
-
def
|
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
|
-
|
544
|
+
clean_copied_contest_dir(dest / preset.contest)
|
545
545
|
if preset.problem is not None:
|
546
|
-
|
546
|
+
clean_copied_problem_dir(dest / preset.problem)
|
547
547
|
|
548
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 =
|
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
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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 =
|
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=
|
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 =
|
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=
|
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 =
|
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
|
-
|
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
|
-
|
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 =
|
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()
|