rbx.cp 0.5.73__py3-none-any.whl → 0.6.1__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/annotations.py +21 -1
- rbx/box/cd.py +11 -1
- rbx/box/checkers.py +9 -1
- rbx/box/cli.py +59 -46
- rbx/box/code.py +142 -3
- rbx/box/contest/build_contest_statements.py +44 -34
- rbx/box/contest/contest_package.py +4 -7
- rbx/box/contest/main.py +7 -58
- rbx/box/contest/schema.py +52 -8
- rbx/box/contest/statements.py +53 -25
- rbx/box/creation.py +3 -36
- rbx/box/environment.py +21 -9
- rbx/box/fields.py +35 -0
- rbx/box/lang.py +27 -0
- rbx/box/linting.py +26 -0
- rbx/box/package.py +4 -35
- rbx/box/packaging/boca/packager.py +48 -5
- rbx/box/packaging/contest_main.py +13 -0
- rbx/box/packaging/main.py +13 -2
- rbx/box/packaging/packager.py +4 -4
- rbx/box/packaging/pkg/packager.py +142 -0
- rbx/box/packaging/polygon/packager.py +2 -24
- rbx/box/packaging/polygon/upload.py +35 -17
- rbx/box/presets/__init__.py +362 -281
- rbx/box/presets/lock_schema.py +1 -2
- rbx/box/presets/schema.py +13 -5
- rbx/box/remote.py +2 -2
- rbx/box/retries.py +8 -0
- rbx/box/schema.py +82 -19
- rbx/box/solutions.py +77 -15
- rbx/box/statements/build_statements.py +44 -27
- rbx/box/statements/builders.py +18 -10
- rbx/box/statements/expander.py +49 -0
- rbx/box/statements/latex_jinja.py +61 -4
- rbx/box/statements/schema.py +33 -9
- rbx/box/stats.py +92 -0
- rbx/box/tasks.py +6 -3
- rbx/box/testcase_utils.py +19 -47
- rbx/box/tooling/__init__.py +0 -0
- rbx/box/tooling/boca/__init__.py +0 -0
- rbx/box/tooling/boca/main.py +13 -0
- rbx/box/tooling/boca/scrape.py +34 -0
- rbx/box/{packaging/boca/upload.py → tooling/boca/scraper.py} +77 -8
- rbx/box/tooling/main.py +8 -0
- rbx/box/ui/utils/run_ui.py +1 -1
- rbx/box/ui/widgets/interaction_box.py +19 -1
- rbx/grading/caching.py +18 -2
- rbx/grading/judge/sandbox.py +60 -5
- rbx/grading/judge/sandboxes/isolate.py +1 -0
- rbx/grading/judge/sandboxes/stupid_sandbox.py +11 -5
- rbx/grading/judge/sandboxes/timeit.py +36 -15
- rbx/grading/processing_context.py +62 -78
- rbx/grading/steps.py +92 -40
- rbx/resources/packagers/boca/checker.sh +4 -1
- rbx/resources/packagers/boca/compile/c +2 -6
- rbx/resources/packagers/boca/compile/cc +2 -6
- rbx/resources/packagers/boca/compile/cpp +2 -6
- rbx/resources/packagers/boca/compile/java +1 -6
- rbx/resources/packagers/boca/compile/kt +24 -28
- rbx/resources/packagers/boca/compile/py2 +2 -6
- rbx/resources/packagers/boca/compile/py3 +2 -6
- rbx/resources/packagers/boca/interactive/c +15 -83
- rbx/resources/packagers/boca/interactive/cc +15 -83
- rbx/resources/packagers/boca/interactive/cpp +15 -83
- rbx/resources/packagers/boca/interactive/java +15 -88
- rbx/resources/packagers/boca/interactive/kt +15 -88
- rbx/resources/packagers/boca/interactive/py2 +15 -88
- rbx/resources/packagers/boca/interactive/py3 +15 -88
- rbx/resources/packagers/boca/interactor_compile.sh +5 -2
- rbx/resources/packagers/boca/interactor_run.sh +174 -0
- rbx/resources/packagers/boca/safeexec.c +530 -0
- rbx/resources/packagers/boca/safeexec_compile.sh +49 -0
- rbx/resources/presets/default/contest/contest.rbx.yml +9 -8
- rbx/resources/presets/default/problem/problem.rbx.yml +38 -26
- rbx/resources/presets/default/problem/random.txt +3 -1
- rbx/resources/presets/default/problem/rbx.h +92 -0
- rbx/resources/presets/default/problem/statement/statement.rbx.tex +4 -7
- rbx/resources/presets/default/problem/validator.cpp +8 -8
- rbx/resources/templates/rbx.h +2 -3
- {rbx_cp-0.5.73.dist-info → rbx_cp-0.6.1.dist-info}/METADATA +23 -6
- {rbx_cp-0.5.73.dist-info → rbx_cp-0.6.1.dist-info}/RECORD +84 -71
- {rbx_cp-0.5.73.dist-info → rbx_cp-0.6.1.dist-info}/WHEEL +1 -1
- rbx/resources/packagers/boca/compile/pas +0 -172
- rbx/resources/presets/default/problem/statement/projecao.png +0 -0
- {rbx_cp-0.5.73.dist-info → rbx_cp-0.6.1.dist-info}/LICENSE +0 -0
- {rbx_cp-0.5.73.dist-info → rbx_cp-0.6.1.dist-info}/entry_points.txt +0 -0
@@ -32,11 +32,11 @@ class BocaPackager(BasePackager):
|
|
32
32
|
def _get_main_statement(self) -> Statement:
|
33
33
|
pkg = package.find_problem_package_or_die()
|
34
34
|
|
35
|
-
if not pkg.
|
35
|
+
if not pkg.expanded_statements:
|
36
36
|
console.console.print('[error]No statements found.[/error]')
|
37
37
|
raise typer.Exit(1)
|
38
38
|
|
39
|
-
return pkg.
|
39
|
+
return pkg.expanded_statements[0]
|
40
40
|
|
41
41
|
def _get_main_built_statement(
|
42
42
|
self, built_statements: List[BuiltStatement]
|
@@ -119,10 +119,18 @@ class BocaPackager(BasePackager):
|
|
119
119
|
|
120
120
|
def _get_limits(self, language: BocaLanguage) -> str:
|
121
121
|
pkg = package.find_problem_package_or_die()
|
122
|
-
|
122
|
+
if pkg.type == TaskType.COMMUNICATION:
|
123
|
+
# Interactive tasks only support a single run.
|
124
|
+
no_of_runs = 1
|
125
|
+
time_limit = f'{self._get_pkg_timelimit(language) / 1000:.2f}'
|
126
|
+
else:
|
127
|
+
no_of_runs = self._get_number_of_runs(language)
|
128
|
+
time_limit = test_time(
|
129
|
+
self._get_pkg_timelimit(language) / 1000 * no_of_runs
|
130
|
+
)
|
123
131
|
return (
|
124
132
|
'#!/bin/bash\n'
|
125
|
-
f'echo {
|
133
|
+
f'echo {time_limit}\n'
|
126
134
|
f'echo {no_of_runs}\n'
|
127
135
|
f'echo {self._get_pkg_memorylimit(language)}\n'
|
128
136
|
f'echo {pkg.outputLimit}\n'
|
@@ -176,6 +184,25 @@ class BocaPackager(BasePackager):
|
|
176
184
|
'{{rbxFlags}}', extension.flags_with_defaults()['cc']
|
177
185
|
).replace('{{interactor_content}}', interactor)
|
178
186
|
|
187
|
+
def _get_safeexec(self) -> str:
|
188
|
+
safeexec_script_path = (
|
189
|
+
get_default_app_path() / 'packagers' / 'boca' / 'safeexec_compile.sh'
|
190
|
+
)
|
191
|
+
safeexec_path = get_default_app_path() / 'packagers' / 'boca' / 'safeexec.c'
|
192
|
+
if not safeexec_script_path.exists():
|
193
|
+
console.console.print(
|
194
|
+
'[error]BOCA template safeexec compile script not found.[/error]'
|
195
|
+
)
|
196
|
+
raise typer.Exit(1)
|
197
|
+
if not safeexec_path.exists():
|
198
|
+
console.console.print(
|
199
|
+
'[error]BOCA template safeexec source code not found.[/error]'
|
200
|
+
)
|
201
|
+
raise typer.Exit(1)
|
202
|
+
return safeexec_script_path.read_text().replace(
|
203
|
+
'{{safeexec_content}}', safeexec_path.read_text()
|
204
|
+
)
|
205
|
+
|
179
206
|
def _get_compile(self, language: BocaLanguage) -> str:
|
180
207
|
pkg = package.find_problem_package_or_die()
|
181
208
|
extension = get_extension_or_default('boca', BocaExtension)
|
@@ -196,6 +223,9 @@ class BocaPackager(BasePackager):
|
|
196
223
|
compile_text = compile_text.replace(
|
197
224
|
'umask 0022', 'umask 0022\n\n' + self._get_interactor()
|
198
225
|
)
|
226
|
+
compile_text = compile_text.replace(
|
227
|
+
'umask 0022', 'umask 0022\n\n' + self._get_safeexec()
|
228
|
+
)
|
199
229
|
compile_text = compile_text.replace(
|
200
230
|
'umask 0022', 'umask 0022\n\n' + self._get_checker()
|
201
231
|
)
|
@@ -206,7 +236,6 @@ class BocaPackager(BasePackager):
|
|
206
236
|
return compile_text
|
207
237
|
|
208
238
|
def _copy_solutions(self, into_path: pathlib.Path):
|
209
|
-
into_path = into_path / 'solutions'
|
210
239
|
for solution in package.get_solutions():
|
211
240
|
dest_path = (
|
212
241
|
into_path
|
@@ -218,6 +247,19 @@ class BocaPackager(BasePackager):
|
|
218
247
|
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
219
248
|
shutil.copy(str(solution.path), dest_path)
|
220
249
|
|
250
|
+
def _expand_run_script(self, run_path: pathlib.Path):
|
251
|
+
pkg = package.find_problem_package_or_die()
|
252
|
+
if pkg.type == TaskType.COMMUNICATION:
|
253
|
+
runit_content = (
|
254
|
+
get_default_app_path() / 'packagers' / 'boca' / 'interactor_run.sh'
|
255
|
+
).read_text()
|
256
|
+
run_path.write_text(
|
257
|
+
run_path.read_text().replace(
|
258
|
+
'{{runit_content}}',
|
259
|
+
runit_content,
|
260
|
+
)
|
261
|
+
)
|
262
|
+
|
221
263
|
@classmethod
|
222
264
|
def name(cls) -> str:
|
223
265
|
return 'boca'
|
@@ -263,6 +305,7 @@ class BocaPackager(BasePackager):
|
|
263
305
|
)
|
264
306
|
raise typer.Exit(1)
|
265
307
|
shutil.copyfile(run_orig_path, run_path / language)
|
308
|
+
self._expand_run_script(run_path / language)
|
266
309
|
|
267
310
|
# Prepare compile.
|
268
311
|
compile_path = into_path / 'compile'
|
@@ -86,3 +86,16 @@ async def polygon(
|
|
86
86
|
await run_contest_packager(
|
87
87
|
PolygonContestPackager, PolygonPackager, verification=verification
|
88
88
|
)
|
89
|
+
|
90
|
+
|
91
|
+
@app.command('pkg', help='Build a contest package for PKG.')
|
92
|
+
@contest_package.within_contest
|
93
|
+
@syncer.sync
|
94
|
+
async def pkg(
|
95
|
+
verification: environment.VerificationParam,
|
96
|
+
):
|
97
|
+
from rbx.box.packaging.pkg.packager import PkgContestPackager, PkgPackager
|
98
|
+
|
99
|
+
await run_contest_packager(
|
100
|
+
PkgContestPackager, PkgPackager, verification=verification
|
101
|
+
)
|
rbx/box/packaging/main.py
CHANGED
@@ -114,9 +114,9 @@ async def boca(
|
|
114
114
|
result_path = await run_packager(BocaPackager, verification=verification)
|
115
115
|
|
116
116
|
if upload:
|
117
|
-
from rbx.box.
|
117
|
+
from rbx.box.tooling.boca.scraper import get_boca_scraper
|
118
118
|
|
119
|
-
uploader =
|
119
|
+
uploader = get_boca_scraper()
|
120
120
|
uploader.login_and_upload(result_path)
|
121
121
|
|
122
122
|
|
@@ -132,3 +132,14 @@ async def moj(
|
|
132
132
|
from rbx.box.packaging.moj.packager import MojPackager
|
133
133
|
|
134
134
|
await run_packager(MojPackager, verification=verification, for_boca=for_boca)
|
135
|
+
|
136
|
+
|
137
|
+
@app.command('pkg', help='Build a package for PKG.')
|
138
|
+
@package.within_problem
|
139
|
+
@syncer.sync
|
140
|
+
async def pkg(
|
141
|
+
verification: environment.VerificationParam,
|
142
|
+
):
|
143
|
+
from rbx.box.packaging.pkg.packager import PkgPackager
|
144
|
+
|
145
|
+
await run_packager(PkgPackager, verification=verification)
|
rbx/box/packaging/packager.py
CHANGED
@@ -46,7 +46,7 @@ class BasePackager(ABC):
|
|
46
46
|
pkg = package.find_problem_package_or_die()
|
47
47
|
|
48
48
|
res = set()
|
49
|
-
for statement in pkg.
|
49
|
+
for statement in pkg.expanded_statements:
|
50
50
|
res.add(statement.language)
|
51
51
|
return list(res)
|
52
52
|
|
@@ -89,7 +89,7 @@ class BasePackager(ABC):
|
|
89
89
|
|
90
90
|
def get_statement_for_language(self, lang: str) -> Statement:
|
91
91
|
pkg = package.find_problem_package_or_die()
|
92
|
-
for statement in pkg.
|
92
|
+
for statement in pkg.expanded_statements:
|
93
93
|
if statement.language == lang:
|
94
94
|
return statement
|
95
95
|
raise
|
@@ -114,7 +114,7 @@ class BaseContestPackager(ABC):
|
|
114
114
|
pkg = contest_package.find_contest_package_or_die()
|
115
115
|
|
116
116
|
res = set()
|
117
|
-
for statement in pkg.
|
117
|
+
for statement in pkg.expanded_statements:
|
118
118
|
res.add(statement.language)
|
119
119
|
return list(res)
|
120
120
|
|
@@ -123,7 +123,7 @@ class BaseContestPackager(ABC):
|
|
123
123
|
|
124
124
|
def get_statement_for_language(self, lang: str) -> ContestStatement:
|
125
125
|
contest = contest_package.find_contest_package_or_die()
|
126
|
-
for statement in contest.
|
126
|
+
for statement in contest.expanded_statements:
|
127
127
|
if statement.language == lang:
|
128
128
|
return statement
|
129
129
|
raise
|
@@ -0,0 +1,142 @@
|
|
1
|
+
import pathlib
|
2
|
+
import shutil
|
3
|
+
from typing import List, Optional
|
4
|
+
|
5
|
+
from rbx.box import naming, package
|
6
|
+
from rbx.box.contest import contest_package
|
7
|
+
from rbx.box.contest.schema import ContestStatement
|
8
|
+
from rbx.box.packaging.packager import (
|
9
|
+
BaseContestPackager,
|
10
|
+
BasePackager,
|
11
|
+
BuiltContestStatement,
|
12
|
+
BuiltProblemPackage,
|
13
|
+
BuiltStatement,
|
14
|
+
)
|
15
|
+
from rbx.box.schema import ExpectedOutcome, TaskType
|
16
|
+
from rbx.box.statements.schema import Statement
|
17
|
+
|
18
|
+
|
19
|
+
class PkgPackager(BasePackager):
|
20
|
+
@classmethod
|
21
|
+
def task_types(cls) -> List[TaskType]:
|
22
|
+
return [TaskType.BATCH, TaskType.COMMUNICATION]
|
23
|
+
|
24
|
+
@classmethod
|
25
|
+
def name(cls) -> str:
|
26
|
+
return 'pkg'
|
27
|
+
|
28
|
+
def _get_problem_basename(self) -> str:
|
29
|
+
shortname = naming.get_problem_shortname()
|
30
|
+
if shortname is not None:
|
31
|
+
return shortname
|
32
|
+
return self.package_basename()
|
33
|
+
|
34
|
+
def _get_main_statement(self) -> Optional[Statement]:
|
35
|
+
pkg = package.find_problem_package_or_die()
|
36
|
+
if not pkg.expanded_statements:
|
37
|
+
return None
|
38
|
+
return pkg.expanded_statements[0]
|
39
|
+
|
40
|
+
def _get_main_built_statement(
|
41
|
+
self, built_statements: List[BuiltStatement]
|
42
|
+
) -> Optional[BuiltStatement]:
|
43
|
+
statement = self._get_main_statement()
|
44
|
+
if statement is None:
|
45
|
+
return None
|
46
|
+
for built_statement in built_statements:
|
47
|
+
if built_statement.statement == statement:
|
48
|
+
return built_statement
|
49
|
+
return None
|
50
|
+
|
51
|
+
def _copy_accepted_solutions(self, into_path: pathlib.Path):
|
52
|
+
for solution in package.get_solutions():
|
53
|
+
if solution.outcome != ExpectedOutcome.ACCEPTED:
|
54
|
+
continue
|
55
|
+
dest_path = into_path / solution.path.name
|
56
|
+
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
57
|
+
shutil.copy(str(solution.path), dest_path)
|
58
|
+
|
59
|
+
def package(
|
60
|
+
self,
|
61
|
+
build_path: pathlib.Path,
|
62
|
+
into_path: pathlib.Path,
|
63
|
+
built_statements: List[BuiltStatement],
|
64
|
+
) -> pathlib.Path:
|
65
|
+
into_path.mkdir(parents=True, exist_ok=True)
|
66
|
+
|
67
|
+
main_statement = self._get_main_built_statement(built_statements)
|
68
|
+
if main_statement is not None:
|
69
|
+
statement_path = into_path / 'statement.pdf'
|
70
|
+
shutil.copyfile(main_statement.path, statement_path)
|
71
|
+
|
72
|
+
# Prepare tests
|
73
|
+
tests_path = into_path / 'tests'
|
74
|
+
tests_path.mkdir(parents=True, exist_ok=True)
|
75
|
+
|
76
|
+
testcases = self.get_flattened_built_testcases()
|
77
|
+
for i, testcase in enumerate(testcases):
|
78
|
+
shutil.copyfile(testcase.inputPath, tests_path / f'{i + 1:03d}.in')
|
79
|
+
if testcase.outputPath is not None:
|
80
|
+
shutil.copyfile(testcase.outputPath, tests_path / f'{i + 1:03d}.ans')
|
81
|
+
else:
|
82
|
+
(tests_path / f'{i + 1:03d}.ans').touch()
|
83
|
+
|
84
|
+
# Copy solutions.
|
85
|
+
solutions_path = into_path / 'solutions'
|
86
|
+
solutions_path.mkdir(parents=True, exist_ok=True)
|
87
|
+
self._copy_accepted_solutions(solutions_path)
|
88
|
+
|
89
|
+
# Zip all.
|
90
|
+
shutil.make_archive(
|
91
|
+
str(build_path / self._get_problem_basename()), 'zip', into_path
|
92
|
+
)
|
93
|
+
|
94
|
+
return (build_path / self._get_problem_basename()).with_suffix('.zip')
|
95
|
+
|
96
|
+
|
97
|
+
class PkgContestPackager(BaseContestPackager):
|
98
|
+
@classmethod
|
99
|
+
def name(cls) -> str:
|
100
|
+
return 'pkg'
|
101
|
+
|
102
|
+
def _get_main_statement(self) -> Optional[ContestStatement]:
|
103
|
+
pkg = contest_package.find_contest_package_or_die()
|
104
|
+
if not pkg.expanded_statements:
|
105
|
+
return None
|
106
|
+
return pkg.expanded_statements[0]
|
107
|
+
|
108
|
+
def _get_main_built_statement(
|
109
|
+
self, built_statements: List[BuiltContestStatement]
|
110
|
+
) -> Optional[BuiltContestStatement]:
|
111
|
+
statement = self._get_main_statement()
|
112
|
+
if statement is None:
|
113
|
+
return None
|
114
|
+
for built_statement in built_statements:
|
115
|
+
if built_statement.statement == statement:
|
116
|
+
return built_statement
|
117
|
+
return None
|
118
|
+
|
119
|
+
def package(
|
120
|
+
self,
|
121
|
+
built_packages: List[BuiltProblemPackage],
|
122
|
+
build_path: pathlib.Path,
|
123
|
+
into_path: pathlib.Path,
|
124
|
+
built_statements: List[BuiltContestStatement],
|
125
|
+
) -> pathlib.Path:
|
126
|
+
into_path.mkdir(parents=True, exist_ok=True)
|
127
|
+
|
128
|
+
# Add contest-level statement.
|
129
|
+
main_statement = self._get_main_built_statement(built_statements)
|
130
|
+
if main_statement is not None:
|
131
|
+
statement_path = into_path / 'statement.pdf'
|
132
|
+
shutil.copyfile(main_statement.path, statement_path)
|
133
|
+
|
134
|
+
# Add problems.
|
135
|
+
for built_package in built_packages:
|
136
|
+
pkg_path = into_path / built_package.problem.short_name
|
137
|
+
shutil.unpack_archive(built_package.path, pkg_path, format='zip')
|
138
|
+
|
139
|
+
# Zip all.
|
140
|
+
shutil.make_archive(str(build_path / 'contest'), 'zip', into_path)
|
141
|
+
|
142
|
+
return (build_path / 'contest').with_suffix('.zip')
|
@@ -1,13 +1,12 @@
|
|
1
|
-
import functools
|
2
1
|
import pathlib
|
3
2
|
import shutil
|
4
3
|
from typing import List, Optional
|
5
4
|
|
6
|
-
import iso639
|
7
5
|
import typer
|
8
6
|
|
9
7
|
from rbx import console
|
10
8
|
from rbx.box import header, package
|
9
|
+
from rbx.box.lang import code_to_langs, is_valid_lang_code
|
11
10
|
from rbx.box.packaging.packager import (
|
12
11
|
BaseContestPackager,
|
13
12
|
BasePackager,
|
@@ -28,27 +27,6 @@ DAT_TEMPLATE = """
|
|
28
27
|
"""
|
29
28
|
|
30
29
|
|
31
|
-
def langs_to_code(langs: List[str]) -> List[str]:
|
32
|
-
return [iso639.Language.from_name(lang).part1 for lang in langs]
|
33
|
-
|
34
|
-
|
35
|
-
def code_to_langs(langs: List[str]) -> List[str]:
|
36
|
-
return [iso639.Language.from_part1(lang).name.lower() for lang in langs]
|
37
|
-
|
38
|
-
|
39
|
-
@functools.cache
|
40
|
-
def is_valid_lang_code(lang: str) -> bool:
|
41
|
-
try:
|
42
|
-
code_to_langs([lang])
|
43
|
-
except iso639.LanguageNotFoundError:
|
44
|
-
console.console.print(
|
45
|
-
f'[warning]Language [item]{lang}[/item] is being skipped because it is not a iso639 language.[/warning]'
|
46
|
-
)
|
47
|
-
return False
|
48
|
-
|
49
|
-
return True
|
50
|
-
|
51
|
-
|
52
30
|
class PolygonPackager(BasePackager):
|
53
31
|
@classmethod
|
54
32
|
def task_types(cls) -> List[TaskType]:
|
@@ -59,7 +37,7 @@ class PolygonPackager(BasePackager):
|
|
59
37
|
pkg = package.find_problem_package_or_die()
|
60
38
|
|
61
39
|
lang_codes = set()
|
62
|
-
for statement in pkg.
|
40
|
+
for statement in pkg.expanded_statements:
|
63
41
|
lang_codes.add(statement.title)
|
64
42
|
|
65
43
|
for lang in langs:
|
@@ -10,8 +10,8 @@ import typer
|
|
10
10
|
from rbx import console
|
11
11
|
from rbx.box import header, package
|
12
12
|
from rbx.box.generators import get_all_built_testcases
|
13
|
+
from rbx.box.lang import code_to_langs, is_valid_lang_code
|
13
14
|
from rbx.box.packaging.polygon import polygon_api as api
|
14
|
-
from rbx.box.packaging.polygon.packager import code_to_langs, is_valid_lang_code
|
15
15
|
from rbx.box.schema import CodeItem, ExpectedOutcome, Solution, TaskType, Testcase
|
16
16
|
from rbx.box.statements.build_statements import get_relative_assets
|
17
17
|
from rbx.box.statements.builders import (
|
@@ -20,7 +20,11 @@ from rbx.box.statements.builders import (
|
|
20
20
|
render_jinja_blocks,
|
21
21
|
)
|
22
22
|
from rbx.box.statements.schema import Statement, StatementType
|
23
|
-
from rbx.box.testcase_utils import
|
23
|
+
from rbx.box.testcase_utils import (
|
24
|
+
TestcaseInteractionParsingError,
|
25
|
+
get_alternate_interaction_texts,
|
26
|
+
parse_interaction,
|
27
|
+
)
|
24
28
|
|
25
29
|
_API_URL = 'https://polygon.codeforces.com/api'
|
26
30
|
|
@@ -163,17 +167,23 @@ def _get_test_params_for_statement(
|
|
163
167
|
|
164
168
|
pio_path = testcase.outputPath.with_suffix('.pio')
|
165
169
|
if pio_path.is_file():
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
170
|
+
try:
|
171
|
+
interaction = parse_interaction(pio_path)
|
172
|
+
except TestcaseInteractionParsingError:
|
173
|
+
pass
|
174
|
+
else:
|
175
|
+
res['test_input_for_statements'], res['test_output_for_statements'] = (
|
176
|
+
get_alternate_interaction_texts(interaction)
|
177
|
+
)
|
178
|
+
return res
|
179
|
+
|
180
|
+
# .pio does not exist or is not parseable, fallback to .pin and .pout.
|
181
|
+
pin_path = testcase.outputPath.with_suffix('.pin')
|
182
|
+
if pin_path.is_file():
|
183
|
+
res['test_input_for_statements'] = pin_path.read_text()
|
184
|
+
pout_path = testcase.outputPath.with_suffix('.pout')
|
185
|
+
if pout_path.is_file():
|
186
|
+
res['test_output_for_statements'] = pout_path.read_text()
|
177
187
|
return res
|
178
188
|
|
179
189
|
|
@@ -229,7 +239,7 @@ def _upload_solutions(problem: api.Problem):
|
|
229
239
|
|
230
240
|
def _get_statement_for_language(language: str) -> Optional[Statement]:
|
231
241
|
pkg = package.find_problem_package_or_die()
|
232
|
-
for statement in pkg.
|
242
|
+
for statement in pkg.expanded_statements:
|
233
243
|
if statement.language == language:
|
234
244
|
return statement
|
235
245
|
return None
|
@@ -290,11 +300,15 @@ def _upload_statement(problem: api.Problem, preserve_language: bool = False):
|
|
290
300
|
pkg = package.find_problem_package_or_die()
|
291
301
|
|
292
302
|
languages = set()
|
293
|
-
for statement in pkg.
|
303
|
+
for statement in pkg.expanded_statements:
|
294
304
|
if not is_valid_lang_code(statement.language):
|
295
305
|
continue
|
296
306
|
languages.add(statement.language)
|
297
|
-
|
307
|
+
|
308
|
+
uploaded_languages = set()
|
309
|
+
|
310
|
+
# Prioritize English statements.
|
311
|
+
for language in ['en'] + list(languages):
|
298
312
|
statement = _get_statement_for_language(language)
|
299
313
|
if statement is None:
|
300
314
|
continue
|
@@ -304,6 +318,10 @@ def _upload_statement(problem: api.Problem, preserve_language: bool = False):
|
|
304
318
|
console.console.print(
|
305
319
|
f'Uploading statement for language [item]{language}[/item] (polygon language: [item]{statement_lang}[/item])...'
|
306
320
|
)
|
321
|
+
uploaded_language = statement_lang if preserve_language else 'english'
|
322
|
+
if uploaded_language in uploaded_languages:
|
323
|
+
continue
|
324
|
+
uploaded_languages.add(uploaded_language)
|
307
325
|
if not preserve_language and statement_lang != 'english':
|
308
326
|
console.console.print(
|
309
327
|
'[warning]By default, Polygon statements are uploaded in English.\n'
|
@@ -322,7 +340,7 @@ def _upload_statement(problem: api.Problem, preserve_language: bool = False):
|
|
322
340
|
notes=_get_notes_with_explanations(blocks) or '',
|
323
341
|
)
|
324
342
|
problem.save_statement(
|
325
|
-
lang=
|
343
|
+
lang=uploaded_language,
|
326
344
|
problem_statement=polygon_statement,
|
327
345
|
)
|
328
346
|
|