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/cli.py
CHANGED
@@ -25,6 +25,7 @@ from rbx.box import (
|
|
25
25
|
presets,
|
26
26
|
setter_config,
|
27
27
|
state,
|
28
|
+
timing,
|
28
29
|
validators,
|
29
30
|
)
|
30
31
|
from rbx.box.contest import main as contest
|
@@ -34,7 +35,6 @@ from rbx.box.header import generate_header
|
|
34
35
|
from rbx.box.packaging import main as packaging
|
35
36
|
from rbx.box.schema import CodeItem, ExpectedOutcome, TestcaseGroup
|
36
37
|
from rbx.box.solutions import (
|
37
|
-
estimate_time_limit,
|
38
38
|
get_exact_matching_solutions,
|
39
39
|
get_matching_solutions,
|
40
40
|
pick_solutions,
|
@@ -107,6 +107,16 @@ app.add_typer(
|
|
107
107
|
)
|
108
108
|
|
109
109
|
|
110
|
+
def version_callback(value: bool) -> None:
|
111
|
+
if value:
|
112
|
+
import importlib.metadata
|
113
|
+
|
114
|
+
version = importlib.metadata.version('rbx.cp')
|
115
|
+
|
116
|
+
console.console.print(f'rbx version {version}')
|
117
|
+
raise typer.Exit()
|
118
|
+
|
119
|
+
|
110
120
|
@app.callback()
|
111
121
|
def main(
|
112
122
|
cache: Annotated[
|
@@ -136,6 +146,9 @@ def main(
|
|
136
146
|
'-p',
|
137
147
|
help='Whether to profile the execution.',
|
138
148
|
),
|
149
|
+
version: Annotated[
|
150
|
+
bool, typer.Option('--version', '-v', callback=version_callback, is_eager=True)
|
151
|
+
] = False,
|
139
152
|
):
|
140
153
|
if cd.is_problem_package() and not package.is_cache_valid():
|
141
154
|
console.console.print(
|
@@ -266,12 +279,6 @@ async def run(
|
|
266
279
|
'-d',
|
267
280
|
help='Whether to print a detailed view of the tests using tables.',
|
268
281
|
),
|
269
|
-
timeit: bool = typer.Option(
|
270
|
-
False,
|
271
|
-
'--time',
|
272
|
-
'-t',
|
273
|
-
help='Whether to use estimate a time limit based on accepted solutions.',
|
274
|
-
),
|
275
282
|
sanitized: bool = typer.Option(
|
276
283
|
False,
|
277
284
|
'--sanitized',
|
@@ -324,20 +331,6 @@ async def run(
|
|
324
331
|
)
|
325
332
|
return
|
326
333
|
|
327
|
-
override_tl = None
|
328
|
-
if timeit:
|
329
|
-
if sanitized:
|
330
|
-
console.console.print(
|
331
|
-
'[error]Sanitizers are known to be time-hungry, so they cannot be used for time estimation.\n'
|
332
|
-
'Remove either the [item]-s[/item] flag or the [item]-t[/item] flag to run solutions without sanitizers.[/error]'
|
333
|
-
)
|
334
|
-
raise typer.Exit(1)
|
335
|
-
|
336
|
-
# Never use sanitizers for time estimation.
|
337
|
-
override_tl = await _time_impl(check=check, detailed=False)
|
338
|
-
if override_tl is None:
|
339
|
-
raise typer.Exit(1)
|
340
|
-
|
341
334
|
if sanitized:
|
342
335
|
console.console.print(
|
343
336
|
'[warning]Sanitizers are running, so the time limit for the problem will be dropped, '
|
@@ -359,7 +352,6 @@ async def run(
|
|
359
352
|
tracked_solutions=tracked_solutions,
|
360
353
|
check=check,
|
361
354
|
verification=VerificationLevel(verification),
|
362
|
-
timelimit_override=override_tl,
|
363
355
|
sanitized=sanitized,
|
364
356
|
)
|
365
357
|
|
@@ -374,51 +366,6 @@ async def run(
|
|
374
366
|
)
|
375
367
|
|
376
368
|
|
377
|
-
async def _time_impl(check: bool, detailed: bool, runs: int = 0) -> Optional[int]:
|
378
|
-
if package.get_main_solution() is None:
|
379
|
-
console.console.print(
|
380
|
-
'[warning]No main solution found, so cannot estimate a time limit.[/warning]'
|
381
|
-
)
|
382
|
-
return None
|
383
|
-
|
384
|
-
verification = VerificationLevel.ALL_SOLUTIONS.value
|
385
|
-
|
386
|
-
with utils.StatusProgress('Running ACCEPTED solutions...') as s:
|
387
|
-
tracked_solutions = OrderedSet(
|
388
|
-
str(solution.path)
|
389
|
-
for solution in get_exact_matching_solutions(ExpectedOutcome.ACCEPTED)
|
390
|
-
)
|
391
|
-
solution_result = run_solutions(
|
392
|
-
progress=s,
|
393
|
-
tracked_solutions=tracked_solutions,
|
394
|
-
check=check,
|
395
|
-
verification=VerificationLevel(verification),
|
396
|
-
timelimit_override=-1, # Unlimited for time limit estimation
|
397
|
-
nruns=runs,
|
398
|
-
)
|
399
|
-
|
400
|
-
console.console.print()
|
401
|
-
console.console.rule(
|
402
|
-
'[status]Run report (for time estimation)[/status]', style='status'
|
403
|
-
)
|
404
|
-
ok = await print_run_report(
|
405
|
-
solution_result,
|
406
|
-
console.console,
|
407
|
-
VerificationLevel(verification),
|
408
|
-
detailed=detailed,
|
409
|
-
skip_printing_limits=True,
|
410
|
-
)
|
411
|
-
|
412
|
-
if not ok:
|
413
|
-
console.console.print(
|
414
|
-
'[error]Failed to run ACCEPTED solutions, so cannot estimate a reliable time limit.[/error]'
|
415
|
-
)
|
416
|
-
return None
|
417
|
-
|
418
|
-
console.console.print()
|
419
|
-
return await estimate_time_limit(console.console, solution_result)
|
420
|
-
|
421
|
-
|
422
369
|
@app.command(
|
423
370
|
'time, t',
|
424
371
|
rich_help_panel='Testing',
|
@@ -444,7 +391,61 @@ async def time(
|
|
444
391
|
'-r',
|
445
392
|
help='Number of runs to perform for each solution. Zero means the config default.',
|
446
393
|
),
|
394
|
+
profile: str = typer.Option(
|
395
|
+
'local',
|
396
|
+
'--profile',
|
397
|
+
'-p',
|
398
|
+
help='Profile to use for time limit estimation.',
|
399
|
+
),
|
400
|
+
integrate: bool = typer.Option(
|
401
|
+
False,
|
402
|
+
'--integrate',
|
403
|
+
'-i',
|
404
|
+
help='Integrate the given limits profile into the package.',
|
405
|
+
),
|
447
406
|
):
|
407
|
+
if integrate:
|
408
|
+
timing.integrate(profile)
|
409
|
+
return
|
410
|
+
|
411
|
+
import questionary
|
412
|
+
|
413
|
+
formula = environment.get_environment().timing.formula
|
414
|
+
timing_choices = [
|
415
|
+
questionary.Choice(
|
416
|
+
f'Estimate time limits based on the formula {formula} (recommended)',
|
417
|
+
value='estimate',
|
418
|
+
),
|
419
|
+
questionary.Choice('Inherit from the package.', value='inherit'),
|
420
|
+
questionary.Choice(
|
421
|
+
'Estimate time limits based on a custom formula.', value='estimate_custom'
|
422
|
+
),
|
423
|
+
questionary.Choice('Provide a custom time limit.', value='custom'),
|
424
|
+
]
|
425
|
+
|
426
|
+
choice = await questionary.select(
|
427
|
+
'Select how you want to define the time limits for the problem.',
|
428
|
+
choices=timing_choices,
|
429
|
+
).ask_async()
|
430
|
+
|
431
|
+
formula = environment.get_environment().timing.formula
|
432
|
+
|
433
|
+
if choice == 'inherit':
|
434
|
+
timing.inherit_time_limits(profile=profile)
|
435
|
+
return
|
436
|
+
elif choice == 'custom':
|
437
|
+
timelimit = await questionary.text(
|
438
|
+
'Enter a custom time limit for the problem (ms).',
|
439
|
+
validate=lambda x: x.isdigit() and int(x) > 0,
|
440
|
+
).ask_async()
|
441
|
+
timing.set_time_limit(int(timelimit), profile=profile)
|
442
|
+
return
|
443
|
+
|
444
|
+
if choice == 'estimate_custom':
|
445
|
+
formula = await questionary.text(
|
446
|
+
'Enter a custom formula for time limit estimation.'
|
447
|
+
).ask_async()
|
448
|
+
|
448
449
|
main_solution = package.get_main_solution()
|
449
450
|
if check and main_solution is None:
|
450
451
|
console.console.print(
|
@@ -458,7 +459,9 @@ async def time(
|
|
458
459
|
if not await builder.build(verification=verification, output=check):
|
459
460
|
return None
|
460
461
|
|
461
|
-
await
|
462
|
+
await timing.compute_time_limits(
|
463
|
+
check, detailed, runs, formula=formula, profile=profile
|
464
|
+
)
|
462
465
|
|
463
466
|
|
464
467
|
@app.command(
|
@@ -850,11 +853,12 @@ async def validate(
|
|
850
853
|
help='Run unit tests for the validator and checker.',
|
851
854
|
)
|
852
855
|
@package.within_problem
|
853
|
-
|
856
|
+
@syncer.sync
|
857
|
+
async def unit_tests():
|
854
858
|
from rbx.box import unit
|
855
859
|
|
856
860
|
with utils.StatusProgress('Running unit tests...') as s:
|
857
|
-
unit.run_unit_tests(s)
|
861
|
+
await unit.run_unit_tests(s)
|
858
862
|
|
859
863
|
|
860
864
|
@app.command(
|
rbx/box/code.py
CHANGED
@@ -3,6 +3,7 @@ import pathlib
|
|
3
3
|
import re
|
4
4
|
import resource
|
5
5
|
import shlex
|
6
|
+
import sys
|
6
7
|
from enum import Enum
|
7
8
|
from pathlib import PosixPath
|
8
9
|
from typing import List, Optional
|
@@ -214,6 +215,8 @@ def _check_stack_limit():
|
|
214
215
|
return
|
215
216
|
if not state.STATE.run_through_cli:
|
216
217
|
return
|
218
|
+
if sys.platform != 'darwin':
|
219
|
+
return
|
217
220
|
soft, hard = resource.RLIM_INFINITY, resource.RLIM_INFINITY
|
218
221
|
|
219
222
|
TARGET = 256 * 1024 * 1024 # 256 MiB
|
@@ -7,12 +7,14 @@ from typing import Any, Dict, List, Optional, Tuple
|
|
7
7
|
import typer
|
8
8
|
|
9
9
|
from rbx import console, testing_utils, utils
|
10
|
-
from rbx.box import cd, package
|
10
|
+
from rbx.box import cd, limits_info, package
|
11
11
|
from rbx.box.contest.contest_package import get_problems
|
12
12
|
from rbx.box.contest.schema import Contest, ContestProblem, ContestStatement
|
13
13
|
from rbx.box.fields import Primitive
|
14
14
|
from rbx.box.formatting import href
|
15
|
-
from rbx.box.
|
15
|
+
from rbx.box.sanitizers import issue_stack
|
16
|
+
from rbx.box.sanitizers.issue_stack import Issue
|
17
|
+
from rbx.box.schema import LimitsProfile, Package, Testcase
|
16
18
|
from rbx.box.statements import build_statements, latex
|
17
19
|
from rbx.box.statements.build_statements import (
|
18
20
|
get_builders,
|
@@ -41,6 +43,7 @@ class ExtractedProblem:
|
|
41
43
|
package: Package
|
42
44
|
statement: Statement
|
43
45
|
problem: ContestProblem
|
46
|
+
limits: LimitsProfile
|
44
47
|
samples: List[Testcase]
|
45
48
|
built_statement: Optional[pathlib.Path] = None
|
46
49
|
|
@@ -52,6 +55,7 @@ class ExtractedProblem:
|
|
52
55
|
|
53
56
|
def get_statement_builder_problem(self) -> StatementBuilderProblem:
|
54
57
|
return StatementBuilderProblem(
|
58
|
+
limits=self.limits,
|
55
59
|
package=self.package,
|
56
60
|
statement=self.statement,
|
57
61
|
samples=StatementSample.from_testcases(self.samples),
|
@@ -60,10 +64,28 @@ class ExtractedProblem:
|
|
60
64
|
)
|
61
65
|
|
62
66
|
|
67
|
+
class StatementBuildIssue(Issue):
|
68
|
+
def __init__(self, problem: ContestProblem):
|
69
|
+
self.problem = problem
|
70
|
+
|
71
|
+
def get_overview_section(self) -> Optional[Tuple[str, ...]]:
|
72
|
+
return ('statement',)
|
73
|
+
|
74
|
+
def get_overview_message(self) -> str:
|
75
|
+
return f'Error building statement for problem [item]{self.problem.short_name}[/item].'
|
76
|
+
|
77
|
+
|
63
78
|
def _get_samples(problem: ContestProblem) -> List[Testcase]:
|
64
79
|
with cd.new_package_cd(problem.get_path()):
|
65
80
|
package.clear_package_cache()
|
66
|
-
|
81
|
+
try:
|
82
|
+
return get_samples()
|
83
|
+
except Exception as e:
|
84
|
+
console.console.print(
|
85
|
+
f'[error]Error getting samples for problem {problem.short_name}: {e}[/error]'
|
86
|
+
)
|
87
|
+
issue_stack.add_issue(StatementBuildIssue(problem))
|
88
|
+
return []
|
67
89
|
|
68
90
|
|
69
91
|
def get_statement_builder_problems(
|
@@ -94,12 +116,12 @@ def get_statement_builder_contest(
|
|
94
116
|
def get_problems_for_statement(
|
95
117
|
contest: Contest,
|
96
118
|
contest_statement: ContestStatement,
|
97
|
-
requires_matching_statement: bool =
|
119
|
+
requires_matching_statement: bool = False,
|
98
120
|
) -> List[ExtractedProblem]:
|
99
121
|
pkgs = get_problems(contest)
|
100
|
-
if not pkgs and
|
122
|
+
if not pkgs and requires_matching_statement:
|
101
123
|
console.console.print(
|
102
|
-
'[error]No problems found in the contest, cannot infer statement type.[/error]'
|
124
|
+
f'[error]No problems found in the contest, cannot infer statement type for statement [item]{contest_statement.name}[/item].[/error]'
|
103
125
|
)
|
104
126
|
raise typer.Exit(1)
|
105
127
|
|
@@ -122,6 +144,9 @@ def get_problems_for_statement(
|
|
122
144
|
raise typer.Exit(1)
|
123
145
|
res.append(
|
124
146
|
ExtractedProblem(
|
147
|
+
limits=limits_info.get_limits_profile(
|
148
|
+
profile=limits_info.get_active_profile(), root=problem.get_path()
|
149
|
+
),
|
125
150
|
package=pkg,
|
126
151
|
statement=matching_statements[0],
|
127
152
|
problem=problem,
|
@@ -137,6 +162,7 @@ def get_builder_problems(
|
|
137
162
|
) -> List[StatementBuilderProblem]:
|
138
163
|
return [
|
139
164
|
StatementBuilderProblem(
|
165
|
+
limits=ex.limits,
|
140
166
|
package=ex.package,
|
141
167
|
statement=ex.statement,
|
142
168
|
samples=StatementSample.from_testcases(ex.samples),
|
@@ -177,22 +203,29 @@ def _build_problem_statements(
|
|
177
203
|
with cd.new_package_cd(extracted_problem.problem.get_path()):
|
178
204
|
package.clear_package_cache()
|
179
205
|
# TODO: respect steps override
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
206
|
+
try:
|
207
|
+
content, _ = build_statements.build_statement_bytes(
|
208
|
+
extracted_problem.statement,
|
209
|
+
extracted_problem.package,
|
210
|
+
output_type=output_type,
|
211
|
+
short_name=extracted_problem.problem.short_name,
|
212
|
+
overridden_params={
|
213
|
+
cfg.type: cfg for cfg in statement.override.configure
|
214
|
+
}
|
215
|
+
if statement.override is not None
|
216
|
+
else {}, # overridden configure params
|
217
|
+
overridden_assets=contest_assets, # overridden assets
|
218
|
+
overridden_params_root=contest_cwd_absolute,
|
219
|
+
use_samples=use_samples,
|
220
|
+
# Use custom var overriding and problem-level overriding.
|
221
|
+
custom_vars=extra_vars,
|
222
|
+
)
|
223
|
+
except Exception as e:
|
224
|
+
console.console.print(
|
225
|
+
f'[error]Error building statement for problem {extracted_problem.problem.short_name}: {e}[/error]'
|
226
|
+
)
|
227
|
+
issue_stack.add_issue(StatementBuildIssue(extracted_problem.problem))
|
228
|
+
continue
|
196
229
|
dest_dir = root / '.problems' / extracted_problem.problem.short_name
|
197
230
|
dest_path = dest_dir / f'statement{output_type.get_file_suffix()}'
|
198
231
|
dest_dir.mkdir(parents=True, exist_ok=True)
|
@@ -292,7 +325,8 @@ def build_statement_rooted(
|
|
292
325
|
if statement.joiner is None:
|
293
326
|
joiner = None
|
294
327
|
extracted_problems = get_problems_for_statement(
|
295
|
-
contest,
|
328
|
+
contest,
|
329
|
+
statement,
|
296
330
|
)
|
297
331
|
else:
|
298
332
|
# Build problem-level statements.
|
@@ -380,6 +414,14 @@ def build_statement(
|
|
380
414
|
statement_path = (pathlib.Path('build') / statement.name).with_suffix(
|
381
415
|
last_output.get_file_suffix()
|
382
416
|
)
|
417
|
+
active_profile = limits_info.get_active_profile()
|
418
|
+
if (
|
419
|
+
active_profile is not None
|
420
|
+
and limits_info.get_saved_limits_profile(active_profile) is not None
|
421
|
+
):
|
422
|
+
statement_path = statement_path.with_stem(
|
423
|
+
f'{statement_path.stem}-{active_profile}'
|
424
|
+
)
|
383
425
|
statement_path.parent.mkdir(parents=True, exist_ok=True)
|
384
426
|
statement_path.write_bytes(typing.cast(bytes, last_content))
|
385
427
|
console.console.print(
|
@@ -10,6 +10,7 @@ from rbx import console, utils
|
|
10
10
|
from rbx.box import cd
|
11
11
|
from rbx.box.contest.schema import Contest
|
12
12
|
from rbx.box.package import find_problem_package_or_die
|
13
|
+
from rbx.box.sanitizers import issue_stack
|
13
14
|
from rbx.box.schema import Package
|
14
15
|
|
15
16
|
YAML_NAME = 'contest.rbx.yml'
|
@@ -60,7 +61,13 @@ def within_contest(func):
|
|
60
61
|
@functools.wraps(func)
|
61
62
|
def wrapper(*args, **kwargs):
|
62
63
|
with cd.new_package_cd(find_contest()):
|
63
|
-
|
64
|
+
issue_level_token = issue_stack.issue_level_var.set(
|
65
|
+
issue_stack.IssueLevel.OVERVIEW
|
66
|
+
)
|
67
|
+
ret = func(*args, **kwargs)
|
68
|
+
issue_stack.print_current_report()
|
69
|
+
issue_stack.issue_level_var.reset(issue_level_token)
|
70
|
+
return ret
|
64
71
|
|
65
72
|
return wrapper
|
66
73
|
|
rbx/box/contest/main.py
CHANGED
@@ -148,7 +148,7 @@ def add(
|
|
148
148
|
|
149
149
|
creation.create(name, preset=preset, path=pathlib.Path(path))
|
150
150
|
|
151
|
-
|
151
|
+
contest_pkg = find_contest_package_or_die()
|
152
152
|
|
153
153
|
ru, contest = contest_package.get_ruyaml()
|
154
154
|
|
@@ -156,10 +156,16 @@ def add(
|
|
156
156
|
'short_name': short_name,
|
157
157
|
'path': path,
|
158
158
|
}
|
159
|
-
if 'problems' not in contest:
|
159
|
+
if 'problems' not in contest or not contest_pkg.problems:
|
160
160
|
contest['problems'] = [item]
|
161
161
|
else:
|
162
|
-
|
162
|
+
idx = 0
|
163
|
+
while (
|
164
|
+
idx < len(contest_pkg.problems)
|
165
|
+
and contest_pkg.problems[idx].short_name <= short_name
|
166
|
+
):
|
167
|
+
idx += 1
|
168
|
+
contest['problems'].insert(idx, item)
|
163
169
|
|
164
170
|
dest = find_contest_yaml()
|
165
171
|
assert dest is not None
|
rbx/box/contest/schema.py
CHANGED
@@ -3,7 +3,14 @@ from typing import Annotated, Dict, List, Optional
|
|
3
3
|
|
4
4
|
from pydantic import AfterValidator, BaseModel, ConfigDict, Field, model_validator
|
5
5
|
|
6
|
-
from rbx.box.fields import
|
6
|
+
from rbx.box.fields import (
|
7
|
+
FNameField,
|
8
|
+
NameField,
|
9
|
+
Primitive,
|
10
|
+
RecVars,
|
11
|
+
Vars,
|
12
|
+
expand_vars,
|
13
|
+
)
|
7
14
|
from rbx.box.statements.expander import expand_statements
|
8
15
|
from rbx.box.statements.schema import (
|
9
16
|
ConversionStep,
|
@@ -27,9 +34,8 @@ def is_unique_by_name(statements: List['ContestStatement']) -> List['ContestStat
|
|
27
34
|
class ProblemStatementOverride(BaseModel):
|
28
35
|
model_config = ConfigDict(extra='forbid')
|
29
36
|
|
30
|
-
configure: List[ConversionStep] = Field(
|
37
|
+
configure: List[Annotated[ConversionStep, Field(discriminator='type')]] = Field(
|
31
38
|
default=[],
|
32
|
-
discriminator='type',
|
33
39
|
description="""
|
34
40
|
Configure how certain conversion steps should happen when applied to the statement file.
|
35
41
|
|
@@ -84,9 +90,8 @@ Joiner to be used to build the statement.
|
|
84
90
|
This determines how problem statements will be joined into a single contest statement.""",
|
85
91
|
)
|
86
92
|
|
87
|
-
steps: List[ConversionStep] = Field(
|
93
|
+
steps: List[Annotated[ConversionStep, Field(discriminator='type')]] = Field(
|
88
94
|
default=[],
|
89
|
-
discriminator='type',
|
90
95
|
description="""
|
91
96
|
Describes a sequence of conversion steps that should be applied to the statement file
|
92
97
|
of this contest.
|
@@ -97,9 +102,8 @@ certain conversion steps to happen.
|
|
97
102
|
""",
|
98
103
|
)
|
99
104
|
|
100
|
-
configure: List[ConversionStep] = Field(
|
105
|
+
configure: List[Annotated[ConversionStep, Field(discriminator='type')]] = Field(
|
101
106
|
default=[],
|
102
|
-
discriminator='type',
|
103
107
|
description="""
|
104
108
|
Configure how certain conversion steps should happen when applied to the statement file of
|
105
109
|
this contest.
|
@@ -133,13 +137,13 @@ Can be glob pattern as well, such as `imgs/*.png`.
|
|
133
137
|
|
134
138
|
# Vars to be re-used in the statement.
|
135
139
|
# - It will be available as \VAR{vars} variable in the contest-level box statement.
|
136
|
-
vars:
|
140
|
+
vars: RecVars = Field(
|
137
141
|
default={}, description='Variables to be re-used across the package.'
|
138
142
|
)
|
139
143
|
|
140
144
|
@property
|
141
|
-
def expanded_vars(self) ->
|
142
|
-
return
|
145
|
+
def expanded_vars(self) -> Vars:
|
146
|
+
return expand_vars(self.vars)
|
143
147
|
|
144
148
|
|
145
149
|
class ContestProblem(BaseModel):
|
@@ -234,7 +238,7 @@ class Contest(BaseModel):
|
|
234
238
|
|
235
239
|
# Vars to be re-used in the statements.
|
236
240
|
# - It will be available as \VAR{vars} variable in the contest-level box statement.
|
237
|
-
vars:
|
241
|
+
vars: RecVars = Field(
|
238
242
|
default={}, description='Variables to be re-used across the package.'
|
239
243
|
)
|
240
244
|
|
@@ -243,5 +247,5 @@ class Contest(BaseModel):
|
|
243
247
|
return expand_statements(self.statements)
|
244
248
|
|
245
249
|
@property
|
246
|
-
def expanded_vars(self) ->
|
247
|
-
return
|
250
|
+
def expanded_vars(self) -> Vars:
|
251
|
+
return expand_vars(self.vars)
|
rbx/box/contest/statements.py
CHANGED
@@ -5,13 +5,17 @@ import typer
|
|
5
5
|
|
6
6
|
from rbx import annotations, console
|
7
7
|
from rbx.box import cd, environment, package
|
8
|
-
from rbx.box.contest.build_contest_statements import
|
8
|
+
from rbx.box.contest.build_contest_statements import (
|
9
|
+
StatementBuildIssue,
|
10
|
+
build_statement,
|
11
|
+
)
|
9
12
|
from rbx.box.contest.contest_package import (
|
10
13
|
find_contest_package_or_die,
|
11
14
|
within_contest,
|
12
15
|
)
|
13
16
|
from rbx.box.contest.schema import ContestStatement
|
14
17
|
from rbx.box.formatting import href
|
18
|
+
from rbx.box.sanitizers import issue_stack
|
15
19
|
from rbx.box.schema import expand_any_vars
|
16
20
|
from rbx.box.statements.schema import StatementType
|
17
21
|
|
@@ -70,13 +74,13 @@ async def build(
|
|
70
74
|
with cd.new_package_cd(problem.get_path()):
|
71
75
|
package.clear_package_cache()
|
72
76
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
77
|
+
try:
|
78
|
+
if not await builder.build(
|
79
|
+
verification=verification, groups=set(['samples']), output=None
|
80
|
+
):
|
81
|
+
issue_stack.add_issue(StatementBuildIssue(problem))
|
82
|
+
except Exception:
|
83
|
+
issue_stack.add_issue(StatementBuildIssue(problem))
|
80
84
|
|
81
85
|
contest = find_contest_package_or_die()
|
82
86
|
|
rbx/box/dump_schemas.py
CHANGED
@@ -7,10 +7,11 @@ from rbx.box.environment import Environment
|
|
7
7
|
from rbx.box.package import Package
|
8
8
|
from rbx.box.presets.lock_schema import PresetLock
|
9
9
|
from rbx.box.presets.schema import Preset
|
10
|
+
from rbx.box.schema import LimitsProfile
|
10
11
|
from rbx.box.statements.schema import Statement
|
11
12
|
from rbx.utils import dump_schema_str
|
12
13
|
|
13
|
-
models = [Package, Environment, Contest, Preset, PresetLock, Statement]
|
14
|
+
models = [Package, Environment, Contest, Preset, PresetLock, Statement, LimitsProfile]
|
14
15
|
|
15
16
|
for model in models:
|
16
17
|
path = pathlib.Path('schemas') / f'{model.__name__}.json'
|
rbx/box/environment.py
CHANGED
@@ -192,7 +192,7 @@ for the environment will be used.""",
|
|
192
192
|
def get_extension(self, name: str, _: Type[T]) -> Optional[T]:
|
193
193
|
if self.extensions is None:
|
194
194
|
return None
|
195
|
-
if hasattr(self.extensions, name):
|
195
|
+
if not hasattr(self.extensions, name):
|
196
196
|
return None
|
197
197
|
return getattr(self.extensions, name)
|
198
198
|
|
rbx/box/fields.py
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
-
from typing import Dict, TypeVar, Union
|
1
|
+
from typing import TYPE_CHECKING, Dict, TypeVar, Union
|
2
2
|
|
3
3
|
from deepmerge import always_merger
|
4
4
|
from pydantic import BaseModel, Field
|
5
|
+
from typing_extensions import TypeAliasType
|
5
6
|
|
6
7
|
|
7
8
|
def NameField(**kwargs):
|
@@ -35,7 +36,12 @@ def merge_pydantic_models(base: T, nxt: T) -> T:
|
|
35
36
|
return base.model_validate(merged_dict)
|
36
37
|
|
37
38
|
|
38
|
-
Primitive = Union[
|
39
|
+
Primitive = Union[int, float, bool, str]
|
40
|
+
Vars = Dict[str, Primitive]
|
41
|
+
if TYPE_CHECKING:
|
42
|
+
RecVars = Dict[str, Union[Primitive, 'RecVars']]
|
43
|
+
else:
|
44
|
+
RecVars = TypeAliasType('RecVars', "Dict[str, Union[Primitive, 'RecVars']]")
|
39
45
|
|
40
46
|
|
41
47
|
def expand_var(value: Primitive) -> Primitive:
|
@@ -55,5 +61,17 @@ def expand_var(value: Primitive) -> Primitive:
|
|
55
61
|
)
|
56
62
|
|
57
63
|
|
58
|
-
def expand_vars(
|
59
|
-
|
64
|
+
def expand_vars(recvars: RecVars) -> Vars:
|
65
|
+
vars = {}
|
66
|
+
|
67
|
+
def solve(rec: RecVars, prefix: str) -> None:
|
68
|
+
nonlocal vars
|
69
|
+
for k, v in rec.items():
|
70
|
+
if isinstance(v, dict):
|
71
|
+
solve(v, f'{prefix}.{k}')
|
72
|
+
else:
|
73
|
+
key = f'{prefix}.{k}'.strip('.')
|
74
|
+
vars[key] = expand_var(v)
|
75
|
+
|
76
|
+
solve(recvars, '')
|
77
|
+
return vars
|