rbx.cp 0.12.0__py3-none-any.whl → 0.13.2__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 +9 -0
- rbx/box/generators.py +41 -13
- rbx/box/header.py +5 -0
- rbx/box/lang.py +25 -12
- rbx/box/packaging/contest_main.py +40 -7
- rbx/box/packaging/importer.py +37 -0
- rbx/box/packaging/main.py +18 -65
- rbx/box/packaging/packager.py +95 -2
- rbx/box/packaging/polygon/importer.py +232 -0
- rbx/box/packaging/polygon/packager.py +36 -5
- rbx/box/packaging/polygon/upload.py +32 -11
- rbx/box/packaging/polygon/xml_schema.py +15 -6
- rbx/box/stresses.py +1 -1
- rbx/box/tooling/converter.py +76 -0
- rbx/box/tooling/main.py +54 -1
- rbx/resources/presets/default/problem/problem.rbx.yml +1 -4
- rbx/resources/presets/default/problem/testplan/random.py +1 -1
- rbx/resources/presets/default/problem/testplan/random.txt +2 -4
- {rbx_cp-0.12.0.dist-info → rbx_cp-0.13.2.dist-info}/METADATA +2 -2
- {rbx_cp-0.12.0.dist-info → rbx_cp-0.13.2.dist-info}/RECORD +23 -20
- {rbx_cp-0.12.0.dist-info → rbx_cp-0.13.2.dist-info}/LICENSE +0 -0
- {rbx_cp-0.12.0.dist-info → rbx_cp-0.13.2.dist-info}/WHEEL +0 -0
- {rbx_cp-0.12.0.dist-info → rbx_cp-0.13.2.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,232 @@
|
|
1
|
+
import pathlib
|
2
|
+
import shutil
|
3
|
+
from typing import List, Optional
|
4
|
+
|
5
|
+
import typer
|
6
|
+
|
7
|
+
from rbx import console, utils
|
8
|
+
from rbx.box import lang
|
9
|
+
from rbx.box.packaging.importer import BaseImporter
|
10
|
+
from rbx.box.packaging.polygon.xml_schema import File, Problem, Statement, Testset
|
11
|
+
from rbx.box.schema import CodeItem, Interactor, Package, TaskType, TestcaseGroup
|
12
|
+
from rbx.box.statements.schema import Statement as BoxStatement
|
13
|
+
from rbx.box.statements.schema import StatementType
|
14
|
+
|
15
|
+
|
16
|
+
def _get_main_testset(problem: Problem) -> Testset:
|
17
|
+
for testset in problem.judging.testsets:
|
18
|
+
if testset.name == 'tests':
|
19
|
+
return testset
|
20
|
+
console.console.print(
|
21
|
+
'[error][item]tests[/item] testset not found[/error]',
|
22
|
+
)
|
23
|
+
raise typer.Exit(1)
|
24
|
+
|
25
|
+
|
26
|
+
def _get_pdf_statements(problem: Problem) -> List[Statement]:
|
27
|
+
statements = []
|
28
|
+
for statement in problem.statements:
|
29
|
+
if statement.type == 'application/pdf':
|
30
|
+
statements.append(statement)
|
31
|
+
return statements
|
32
|
+
|
33
|
+
|
34
|
+
def _get_statement_path(statement: Statement) -> pathlib.Path:
|
35
|
+
return pathlib.Path('statements') / f'{statement.language}.pdf'
|
36
|
+
|
37
|
+
|
38
|
+
def _populate_tests(
|
39
|
+
testset: Testset, pkg: Package, pkg_path: pathlib.Path, into_path: pathlib.Path
|
40
|
+
):
|
41
|
+
if not testset.answerPattern:
|
42
|
+
console.console.print(
|
43
|
+
'[error][item]answer pattern[/item] not found for testset[/error]',
|
44
|
+
)
|
45
|
+
raise typer.Exit(1)
|
46
|
+
|
47
|
+
for d, test in enumerate(testset.tests):
|
48
|
+
folder_name = 'tests/samples' if test.sample else 'tests/tests'
|
49
|
+
i = d + 1
|
50
|
+
|
51
|
+
input_path = pathlib.Path(testset.inputPattern % i)
|
52
|
+
dest_input_path = into_path / folder_name / f'{i:03d}.in'
|
53
|
+
dest_input_path.parent.mkdir(parents=True, exist_ok=True)
|
54
|
+
shutil.copy(pkg_path / input_path, dest_input_path)
|
55
|
+
|
56
|
+
answer_path = pathlib.Path(testset.answerPattern % i)
|
57
|
+
dest_answer_path = into_path / folder_name / f'{i:03d}.ans'
|
58
|
+
dest_answer_path.parent.mkdir(parents=True, exist_ok=True)
|
59
|
+
shutil.copy(pkg_path / answer_path, dest_answer_path)
|
60
|
+
|
61
|
+
pkg.testcases = [
|
62
|
+
TestcaseGroup(
|
63
|
+
name='samples',
|
64
|
+
testcaseGlob='tests/samples/*.in',
|
65
|
+
),
|
66
|
+
TestcaseGroup(
|
67
|
+
name='tests',
|
68
|
+
testcaseGlob='tests/tests/*.in',
|
69
|
+
),
|
70
|
+
]
|
71
|
+
|
72
|
+
|
73
|
+
def _populate_statements(
|
74
|
+
problem: Problem,
|
75
|
+
pkg: Package,
|
76
|
+
pkg_path: pathlib.Path,
|
77
|
+
into_path: pathlib.Path,
|
78
|
+
main_language: Optional[str] = None,
|
79
|
+
):
|
80
|
+
name_per_language = {name.language: name for name in problem.names}
|
81
|
+
pdf_statements = _get_pdf_statements(problem)
|
82
|
+
pkg_statements = []
|
83
|
+
found_main = False
|
84
|
+
|
85
|
+
for statement in pdf_statements:
|
86
|
+
if statement.language not in name_per_language:
|
87
|
+
continue
|
88
|
+
name = name_per_language[statement.language]
|
89
|
+
statement_path = into_path / _get_statement_path(statement)
|
90
|
+
statement_path.parent.mkdir(parents=True, exist_ok=True)
|
91
|
+
shutil.copy(pkg_path / statement.path, statement_path)
|
92
|
+
|
93
|
+
iso639_code = lang.lang_to_code(statement.language)
|
94
|
+
|
95
|
+
pkg_statement = BoxStatement(
|
96
|
+
name=f'statement-{name.language}',
|
97
|
+
title=name.value,
|
98
|
+
language=iso639_code,
|
99
|
+
path=_get_statement_path(statement),
|
100
|
+
type=StatementType.PDF,
|
101
|
+
)
|
102
|
+
|
103
|
+
if (
|
104
|
+
main_language is not None
|
105
|
+
and main_language == iso639_code
|
106
|
+
and not found_main
|
107
|
+
):
|
108
|
+
# If main statement, add it to the front of the list
|
109
|
+
pkg_statements = [pkg_statement] + pkg_statements
|
110
|
+
found_main = True
|
111
|
+
continue
|
112
|
+
|
113
|
+
if name.main and not found_main:
|
114
|
+
# If main statement, add it to the front of the list
|
115
|
+
pkg_statements = [pkg_statement] + pkg_statements
|
116
|
+
found_main = True
|
117
|
+
continue
|
118
|
+
|
119
|
+
pkg_statements.append(pkg_statement)
|
120
|
+
|
121
|
+
pkg.statements = pkg_statements
|
122
|
+
|
123
|
+
if main_language is not None and not found_main:
|
124
|
+
console.console.print(
|
125
|
+
f'[error]Main statement of language [item]{main_language}[/item] not found.[/error]',
|
126
|
+
)
|
127
|
+
raise typer.Exit(1)
|
128
|
+
|
129
|
+
|
130
|
+
def _is_cpp_source(source: File) -> bool:
|
131
|
+
if source.type is None:
|
132
|
+
return False
|
133
|
+
return 'cpp' in source.type
|
134
|
+
|
135
|
+
|
136
|
+
def _copy_checker(
|
137
|
+
problem: Problem, pkg: Package, pkg_path: pathlib.Path, into_path: pathlib.Path
|
138
|
+
):
|
139
|
+
if problem.checker is None:
|
140
|
+
return
|
141
|
+
if problem.checker.type != 'testlib' or not _is_cpp_source(problem.checker.source):
|
142
|
+
console.console.print(
|
143
|
+
f'[error][item]checker type[/item] not supported: [item]{problem.checker.type}[/item][/error]',
|
144
|
+
)
|
145
|
+
raise typer.Exit(1)
|
146
|
+
shutil.copy(pkg_path / problem.checker.source.path, into_path / 'checker.cpp')
|
147
|
+
|
148
|
+
pkg.checker = CodeItem(
|
149
|
+
path=pathlib.Path('checker.cpp'),
|
150
|
+
)
|
151
|
+
|
152
|
+
|
153
|
+
def _copy_interactor(
|
154
|
+
problem: Problem, pkg: Package, pkg_path: pathlib.Path, into_path: pathlib.Path
|
155
|
+
):
|
156
|
+
if problem.interactor is None:
|
157
|
+
return
|
158
|
+
shutil.copy(pkg_path / problem.interactor.source.path, into_path / 'interactor.cpp')
|
159
|
+
|
160
|
+
if not _is_cpp_source(problem.interactor.source):
|
161
|
+
console.console.print(
|
162
|
+
f'[error]Only C++ interactor is supported, got [item]{problem.interactor.source.type}[/item][/error]',
|
163
|
+
)
|
164
|
+
raise typer.Exit(1)
|
165
|
+
|
166
|
+
pkg.type = TaskType.COMMUNICATION
|
167
|
+
pkg.interactor = Interactor(
|
168
|
+
path=pathlib.Path('interactor.cpp'),
|
169
|
+
legacy=True,
|
170
|
+
)
|
171
|
+
|
172
|
+
|
173
|
+
def _copy_headers(
|
174
|
+
problem: Problem, pkg: Package, pkg_path: pathlib.Path, into_path: pathlib.Path
|
175
|
+
):
|
176
|
+
headers = []
|
177
|
+
for file in problem.files:
|
178
|
+
if file.type is None or not file.type.startswith('h.'):
|
179
|
+
continue
|
180
|
+
header_path = pkg_path / file.path
|
181
|
+
dest_path = into_path / header_path.name
|
182
|
+
if header_path.name == 'rbx.h':
|
183
|
+
dest_path = into_path / 'rbx.override.h'
|
184
|
+
shutil.copy(header_path, dest_path)
|
185
|
+
headers.append(dest_path.name)
|
186
|
+
|
187
|
+
if pkg.checker is not None:
|
188
|
+
pkg.checker.compilationFiles = headers
|
189
|
+
|
190
|
+
if pkg.interactor is not None:
|
191
|
+
pkg.interactor.compilationFiles = headers
|
192
|
+
|
193
|
+
|
194
|
+
class PolygonImporter(BaseImporter):
|
195
|
+
def __init__(self, main_language: Optional[str]):
|
196
|
+
self.main_language = main_language
|
197
|
+
|
198
|
+
@classmethod
|
199
|
+
def name(cls) -> str:
|
200
|
+
return 'polygon'
|
201
|
+
|
202
|
+
async def import_package(self, pkg_path: pathlib.Path, into_path: pathlib.Path):
|
203
|
+
problem_xml = pkg_path / 'problem.xml'
|
204
|
+
if not problem_xml.exists():
|
205
|
+
console.console.print(
|
206
|
+
'[error][item]problem.xml[/item] not found[/error]',
|
207
|
+
)
|
208
|
+
raise typer.Exit(1)
|
209
|
+
|
210
|
+
problem = Problem.from_xml(problem_xml.read_bytes())
|
211
|
+
testset = _get_main_testset(problem)
|
212
|
+
|
213
|
+
if testset.timelimit is None:
|
214
|
+
testset.timelimit = 1000
|
215
|
+
|
216
|
+
if testset.memorylimit is None:
|
217
|
+
testset.memorylimit = 256 * 1024 * 1024
|
218
|
+
|
219
|
+
pkg = Package(
|
220
|
+
name=problem.short_name,
|
221
|
+
timeLimit=testset.timelimit,
|
222
|
+
memoryLimit=testset.memorylimit // (1024 * 1024),
|
223
|
+
outputLimit=64 * 1024,
|
224
|
+
)
|
225
|
+
|
226
|
+
_populate_tests(testset, pkg, pkg_path, into_path)
|
227
|
+
_populate_statements(problem, pkg, pkg_path, into_path, self.main_language)
|
228
|
+
_copy_checker(problem, pkg, pkg_path, into_path)
|
229
|
+
_copy_interactor(problem, pkg, pkg_path, into_path)
|
230
|
+
_copy_headers(problem, pkg, pkg_path, into_path)
|
231
|
+
|
232
|
+
(into_path / 'problem.rbx.yml').write_text(utils.model_to_yaml(pkg))
|
@@ -6,7 +6,7 @@ import typer
|
|
6
6
|
|
7
7
|
from rbx import console, utils
|
8
8
|
from rbx.box import header, package
|
9
|
-
from rbx.box.lang import code_to_langs, is_valid_lang_code
|
9
|
+
from rbx.box.lang import code_to_lang, code_to_langs, is_valid_lang_code
|
10
10
|
from rbx.box.packaging.packager import (
|
11
11
|
BaseContestPackager,
|
12
12
|
BasePackager,
|
@@ -27,7 +27,31 @@ DAT_TEMPLATE = """
|
|
27
27
|
"""
|
28
28
|
|
29
29
|
|
30
|
+
def _select_main_language(
|
31
|
+
names: List[polygon_schema.Name], main_language: Optional[str]
|
32
|
+
):
|
33
|
+
if names:
|
34
|
+
if main_language is not None:
|
35
|
+
lang_name = code_to_lang(main_language)
|
36
|
+
found = False
|
37
|
+
for name in names:
|
38
|
+
if name.language == lang_name:
|
39
|
+
name.main = True
|
40
|
+
found = True
|
41
|
+
break
|
42
|
+
if not found:
|
43
|
+
console.console.print(
|
44
|
+
f'[error]Main language [item]{main_language}[/item] not found.[/error]'
|
45
|
+
)
|
46
|
+
raise typer.Exit(1)
|
47
|
+
else:
|
48
|
+
names[0].main = True
|
49
|
+
|
50
|
+
|
30
51
|
class PolygonPackager(BasePackager):
|
52
|
+
def __init__(self, main_language: Optional[str] = None):
|
53
|
+
self.main_language = main_language
|
54
|
+
|
31
55
|
@classmethod
|
32
56
|
def task_types(cls) -> List[TaskType]:
|
33
57
|
return [TaskType.BATCH, TaskType.COMMUNICATION]
|
@@ -49,7 +73,7 @@ class PolygonPackager(BasePackager):
|
|
49
73
|
raise typer.Exit(1)
|
50
74
|
|
51
75
|
def _get_names(self) -> List[polygon_schema.Name]:
|
52
|
-
|
76
|
+
names = [
|
53
77
|
polygon_schema.Name(
|
54
78
|
language=code_to_langs([lang])[0],
|
55
79
|
value=self.get_statement_for_language(lang).title,
|
@@ -57,6 +81,10 @@ class PolygonPackager(BasePackager):
|
|
57
81
|
for lang in self.languages()
|
58
82
|
]
|
59
83
|
|
84
|
+
_select_main_language(names, self.main_language)
|
85
|
+
|
86
|
+
return names
|
87
|
+
|
60
88
|
def _get_checker(self) -> polygon_schema.Checker:
|
61
89
|
# TODO: support other checker languages
|
62
90
|
return polygon_schema.Checker(
|
@@ -155,6 +183,7 @@ class PolygonPackager(BasePackager):
|
|
155
183
|
pkg = package.find_problem_package_or_die()
|
156
184
|
|
157
185
|
problem = polygon_schema.Problem(
|
186
|
+
short_name=pkg.name,
|
158
187
|
names=self._get_names(),
|
159
188
|
checker=self._get_checker(),
|
160
189
|
judging=self._get_judging(),
|
@@ -209,6 +238,9 @@ class PolygonPackager(BasePackager):
|
|
209
238
|
|
210
239
|
|
211
240
|
class PolygonContestPackager(BaseContestPackager):
|
241
|
+
def __init__(self, main_language: Optional[str] = None):
|
242
|
+
self.main_language = main_language
|
243
|
+
|
212
244
|
@classmethod
|
213
245
|
def name(cls) -> str:
|
214
246
|
return 'polygon'
|
@@ -222,8 +254,7 @@ class PolygonContestPackager(BaseContestPackager):
|
|
222
254
|
for lang in self.languages()
|
223
255
|
if is_valid_lang_code(lang)
|
224
256
|
]
|
225
|
-
|
226
|
-
names[0].main = True
|
257
|
+
_select_main_language(names, self.main_language)
|
227
258
|
return names
|
228
259
|
|
229
260
|
def _process_statement(
|
@@ -319,4 +350,4 @@ class PolygonContestPackager(BaseContestPackager):
|
|
319
350
|
# Zip all.
|
320
351
|
shutil.make_archive(str(build_path / 'contest'), 'zip', into_path)
|
321
352
|
|
322
|
-
return
|
353
|
+
return build_path / 'contest.zip'
|
@@ -295,19 +295,32 @@ def _upload_statement_resources(problem: api.Problem, statement: Statement):
|
|
295
295
|
)
|
296
296
|
|
297
297
|
|
298
|
-
def _upload_statement(
|
298
|
+
def _upload_statement(
|
299
|
+
problem: api.Problem, main_language: Optional[str], upload_as_english: bool = False
|
300
|
+
):
|
299
301
|
pkg = package.find_problem_package_or_die()
|
300
302
|
|
303
|
+
lang_list = []
|
301
304
|
languages = set()
|
302
305
|
for statement in pkg.expanded_statements:
|
303
306
|
if not is_valid_lang_code(statement.language):
|
304
307
|
continue
|
305
308
|
languages.add(statement.language)
|
306
|
-
|
309
|
+
lang_list.append(statement.language)
|
307
310
|
uploaded_languages = set()
|
308
311
|
|
312
|
+
if main_language is None:
|
313
|
+
main_language = lang_list[0]
|
314
|
+
|
315
|
+
# Put the main language first.
|
316
|
+
lang_list = list(languages)
|
317
|
+
for i in range(len(lang_list)):
|
318
|
+
if lang_list[i] == main_language:
|
319
|
+
lang_list[i], lang_list[0] = lang_list[0], lang_list[i]
|
320
|
+
break
|
321
|
+
|
309
322
|
# Prioritize English statements.
|
310
|
-
for language in
|
323
|
+
for language in lang_list:
|
311
324
|
statement = _get_statement_for_language(language)
|
312
325
|
if statement is None:
|
313
326
|
continue
|
@@ -317,15 +330,19 @@ def _upload_statement(problem: api.Problem, preserve_language: bool = False):
|
|
317
330
|
console.console.print(
|
318
331
|
f'Uploading statement for language [item]{language}[/item] (polygon language: [item]{statement_lang}[/item])...'
|
319
332
|
)
|
320
|
-
uploaded_language = statement_lang
|
333
|
+
uploaded_language = statement_lang
|
334
|
+
if main_language == language:
|
335
|
+
if not upload_as_english:
|
336
|
+
console.console.print(
|
337
|
+
'[warning]By default, Polygon statements are uploaded respecting their original language.\n'
|
338
|
+
'Codeforces does not work well with statements in other languages. If you want a better experience, '
|
339
|
+
'use the [item]--upload-as-english[/item] option to force the main statement to be uploaded in English.[/warning]'
|
340
|
+
)
|
341
|
+
else:
|
342
|
+
uploaded_language = 'english'
|
321
343
|
if uploaded_language in uploaded_languages:
|
322
344
|
continue
|
323
345
|
uploaded_languages.add(uploaded_language)
|
324
|
-
if not preserve_language and statement_lang != 'english':
|
325
|
-
console.console.print(
|
326
|
-
'[warning]By default, Polygon statements are uploaded in English.\n'
|
327
|
-
'If you want to preserve the original language of your statement, use [item]--preserve-language[/item].'
|
328
|
-
)
|
329
346
|
blocks = _get_statement_blocks(statement)
|
330
347
|
polygon_statement = api.Statement(
|
331
348
|
encoding='utf-8',
|
@@ -350,7 +367,9 @@ def _normalize_problem_name(name: str) -> str:
|
|
350
367
|
return name.replace(' ', '-').replace('_', '-').lower()
|
351
368
|
|
352
369
|
|
353
|
-
async def upload_problem(
|
370
|
+
async def upload_problem(
|
371
|
+
name: str, main_language: Optional[str], upload_as_english: bool = False
|
372
|
+
):
|
354
373
|
pkg = package.find_problem_package_or_die()
|
355
374
|
name = _normalize_problem_name(name)
|
356
375
|
problem = _find_or_create_problem(name)
|
@@ -369,7 +388,9 @@ async def upload_problem(name: str, preserve_language: bool = False):
|
|
369
388
|
|
370
389
|
_upload_solutions(problem)
|
371
390
|
_upload_testcases(problem)
|
372
|
-
_upload_statement(
|
391
|
+
_upload_statement(
|
392
|
+
problem, main_language=main_language, upload_as_english=upload_as_english
|
393
|
+
)
|
373
394
|
|
374
395
|
# Commit.
|
375
396
|
console.console.print('Committing changes...')
|
@@ -10,7 +10,7 @@ class Name(BaseXmlModel):
|
|
10
10
|
|
11
11
|
|
12
12
|
class Statement(BaseXmlModel):
|
13
|
-
charset: Optional[
|
13
|
+
charset: Optional[str] = attr(default=None)
|
14
14
|
|
15
15
|
language: str = attr()
|
16
16
|
|
@@ -43,9 +43,12 @@ class Testset(BaseXmlModel):
|
|
43
43
|
size: int = element('test-count', default=None)
|
44
44
|
|
45
45
|
inputPattern: str = element('input-path-pattern')
|
46
|
-
|
46
|
+
outputPattern: Optional[str] = element('output-path-pattern', default=None)
|
47
|
+
answerPattern: Optional[str] = element('answer-path-pattern', default=None)
|
47
48
|
|
48
|
-
tests: List[Test] = wrapped(
|
49
|
+
tests: List[Test] = wrapped(
|
50
|
+
'tests', element(tag='test', default=None), default_factory=list
|
51
|
+
)
|
49
52
|
|
50
53
|
|
51
54
|
class Judging(BaseXmlModel):
|
@@ -56,7 +59,7 @@ class Judging(BaseXmlModel):
|
|
56
59
|
|
57
60
|
|
58
61
|
class Checker(BaseXmlModel):
|
59
|
-
name: str = attr()
|
62
|
+
name: Optional[str] = attr(default=None)
|
60
63
|
type: Literal['testlib'] = attr()
|
61
64
|
source: File = element()
|
62
65
|
binary: Optional[File] = element(default=None)
|
@@ -70,6 +73,8 @@ class Interactor(BaseXmlModel):
|
|
70
73
|
|
71
74
|
|
72
75
|
class Problem(BaseXmlModel, tag='problem'):
|
76
|
+
short_name: str = attr('short-name')
|
77
|
+
|
73
78
|
names: List[Name] = wrapped('names', element(tag='name'), default_factory=list)
|
74
79
|
|
75
80
|
statements: List[Statement] = wrapped(
|
@@ -86,9 +91,13 @@ class Problem(BaseXmlModel, tag='problem'):
|
|
86
91
|
default=[],
|
87
92
|
)
|
88
93
|
|
89
|
-
checker: Checker = wrapped(
|
94
|
+
checker: Optional[Checker] = wrapped(
|
95
|
+
'assets', element(tag='checker', default=None), default=None
|
96
|
+
)
|
90
97
|
|
91
|
-
interactor: Optional[Interactor] = wrapped(
|
98
|
+
interactor: Optional[Interactor] = wrapped(
|
99
|
+
'assets', element(tag='interactor', default=None), default=None
|
100
|
+
)
|
92
101
|
|
93
102
|
|
94
103
|
class ContestProblem(BaseXmlModel):
|
rbx/box/stresses.py
CHANGED
@@ -0,0 +1,76 @@
|
|
1
|
+
import pathlib
|
2
|
+
import tempfile
|
3
|
+
from typing import Optional
|
4
|
+
|
5
|
+
import typer
|
6
|
+
|
7
|
+
from rbx import console
|
8
|
+
from rbx.box import builder, cd, package
|
9
|
+
from rbx.box.environment import VerificationLevel
|
10
|
+
from rbx.box.packaging.boca.packager import BocaPackager
|
11
|
+
from rbx.box.packaging.importer import BaseImporter
|
12
|
+
from rbx.box.packaging.moj.packager import MojPackager
|
13
|
+
from rbx.box.packaging.packager import BasePackager, BuiltStatement
|
14
|
+
from rbx.box.packaging.polygon.importer import PolygonImporter
|
15
|
+
from rbx.box.packaging.polygon.packager import PolygonPackager
|
16
|
+
from rbx.box.statements.build_statements import build_statement
|
17
|
+
|
18
|
+
PACKAGER_REGISTRY = {
|
19
|
+
'polygon': PolygonPackager,
|
20
|
+
'boca': BocaPackager,
|
21
|
+
'moj': MojPackager,
|
22
|
+
}
|
23
|
+
|
24
|
+
IMPORTER_REGISTRY = {
|
25
|
+
'polygon': PolygonImporter,
|
26
|
+
}
|
27
|
+
|
28
|
+
|
29
|
+
def get_packager(source: str, **kwargs) -> BasePackager:
|
30
|
+
if source not in PACKAGER_REGISTRY:
|
31
|
+
console.console.print(f'Unknown packager: {source}')
|
32
|
+
raise typer.Exit(1)
|
33
|
+
return PACKAGER_REGISTRY[source](**kwargs)
|
34
|
+
|
35
|
+
|
36
|
+
def get_importer(source: str, **kwargs) -> BaseImporter:
|
37
|
+
if source not in IMPORTER_REGISTRY:
|
38
|
+
console.console.print(f'Unknown importer: {source}')
|
39
|
+
raise typer.Exit(1)
|
40
|
+
return IMPORTER_REGISTRY[source](**kwargs)
|
41
|
+
|
42
|
+
|
43
|
+
async def convert(
|
44
|
+
pkg_dir: pathlib.Path,
|
45
|
+
into_dir: pathlib.Path,
|
46
|
+
source: str,
|
47
|
+
destination: str,
|
48
|
+
main_language: Optional[str] = None,
|
49
|
+
) -> pathlib.Path:
|
50
|
+
importer = get_importer(source, main_language=main_language)
|
51
|
+
packager = get_packager(destination)
|
52
|
+
await importer.import_package(pkg_dir, into_dir)
|
53
|
+
|
54
|
+
with cd.new_package_cd(into_dir):
|
55
|
+
package.clear_package_cache()
|
56
|
+
|
57
|
+
pkg = package.find_problem_package_or_die()
|
58
|
+
|
59
|
+
if not await builder.build(VerificationLevel.NONE.value):
|
60
|
+
console.console.print('[error]Failed to build the problem.[/error]')
|
61
|
+
raise typer.Exit(1)
|
62
|
+
|
63
|
+
built_statements = []
|
64
|
+
for statement_type in packager.statement_types():
|
65
|
+
for language in packager.languages():
|
66
|
+
statement = packager.get_statement_for_language(language)
|
67
|
+
statement_path = build_statement(statement, pkg, statement_type)
|
68
|
+
built_statements.append(
|
69
|
+
BuiltStatement(statement, statement_path, statement_type)
|
70
|
+
)
|
71
|
+
|
72
|
+
with tempfile.TemporaryDirectory() as td:
|
73
|
+
result_path = packager.package(
|
74
|
+
package.get_build_path(), pathlib.Path(td), built_statements
|
75
|
+
)
|
76
|
+
return result_path
|
rbx/box/tooling/main.py
CHANGED
@@ -1,8 +1,61 @@
|
|
1
|
+
import atexit
|
2
|
+
import pathlib
|
3
|
+
import shutil
|
4
|
+
import tempfile
|
5
|
+
import zipfile
|
6
|
+
from typing import Annotated, Optional
|
7
|
+
|
8
|
+
import syncer
|
1
9
|
import typer
|
2
10
|
|
3
|
-
from rbx import annotations
|
11
|
+
from rbx import annotations, console
|
12
|
+
from rbx.box.tooling import converter
|
4
13
|
from rbx.box.tooling.boca import main as boca_main
|
5
14
|
|
6
15
|
app = typer.Typer(no_args_is_help=True, cls=annotations.AliasGroup)
|
7
16
|
|
8
17
|
app.add_typer(boca_main.app, name='boca')
|
18
|
+
|
19
|
+
|
20
|
+
@app.command('convert')
|
21
|
+
@syncer.sync
|
22
|
+
async def convert(
|
23
|
+
pkg: Annotated[pathlib.Path, typer.Argument(help='The package to convert.')],
|
24
|
+
source: Annotated[
|
25
|
+
str, typer.Option('-s', '--source', help='The format to convert from.')
|
26
|
+
],
|
27
|
+
dest: Annotated[
|
28
|
+
str, typer.Option('-d', '--dest', help='The format to convert to.')
|
29
|
+
],
|
30
|
+
output: Annotated[str, typer.Option('-o', '--output', help='The output path.')],
|
31
|
+
language: Annotated[
|
32
|
+
Optional[str],
|
33
|
+
typer.Option('--language', '-l', help='The main language of the problem.'),
|
34
|
+
] = None,
|
35
|
+
):
|
36
|
+
if pkg.suffix == '.zip':
|
37
|
+
temp_dir = tempfile.TemporaryDirectory()
|
38
|
+
with zipfile.ZipFile(pkg, 'r') as zip_ref:
|
39
|
+
zip_ref.extractall(temp_dir.name)
|
40
|
+
pkg = pathlib.Path(temp_dir.name)
|
41
|
+
|
42
|
+
atexit.register(temp_dir.cleanup)
|
43
|
+
|
44
|
+
if not pkg.is_dir():
|
45
|
+
console.console.print(f'[error]Package {pkg} is not a directory.[/error]')
|
46
|
+
raise typer.Exit(1)
|
47
|
+
|
48
|
+
with tempfile.TemporaryDirectory() as td:
|
49
|
+
result_path = await converter.convert(
|
50
|
+
pkg, pathlib.Path(td), source, dest, main_language=language
|
51
|
+
)
|
52
|
+
output_path = pathlib.Path(output)
|
53
|
+
if output_path.suffix == '.zip':
|
54
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
55
|
+
shutil.copy(result_path, output_path)
|
56
|
+
else:
|
57
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
58
|
+
shutil.unpack_archive(result_path, output_path)
|
59
|
+
console.console.print(
|
60
|
+
f'[success]Converted package to [item]{output_path}[/item].[/success]'
|
61
|
+
)
|
@@ -5,9 +5,6 @@ timeLimit: 1000 # ms
|
|
5
5
|
memoryLimit: 256 # MiB
|
6
6
|
checker: {path: "wcmp.cpp"} # Download others from testlib with `rbx download checker`
|
7
7
|
validator: {path: "validator.cpp"}
|
8
|
-
generators:
|
9
|
-
- path: "gens/gen.cpp"
|
10
|
-
name: "gen"
|
11
8
|
testcases:
|
12
9
|
- name: "samples"
|
13
10
|
testcaseGlob: "manual_tests/samples/*.in" # Pattern for the sample inputs.
|
@@ -39,7 +36,7 @@ statements:
|
|
39
36
|
stresses:
|
40
37
|
- name: "stress"
|
41
38
|
generator:
|
42
|
-
name: "gen"
|
39
|
+
name: "gens/gen"
|
43
40
|
args: "[1..<MAX_N>] @" # `@` generates a random string
|
44
41
|
finder: "[sols/wa.cpp] ~ INCORRECT"
|
45
42
|
unitTests:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: rbx.cp
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.13.2
|
4
4
|
Summary:
|
5
5
|
Author: Roberto Sales
|
6
6
|
Requires-Python: >=3.9.1,<4.0.0
|
@@ -20,6 +20,7 @@ Requires-Dist: fastapi (>=0.115.8,<0.116.0)
|
|
20
20
|
Requires-Dist: filelock (>=3.14.0,<4.0.0)
|
21
21
|
Requires-Dist: gitignore-parser (>=0.1.12,<0.2.0)
|
22
22
|
Requires-Dist: gitpython (>=3.1.43,<4.0.0)
|
23
|
+
Requires-Dist: iso639-lang (>=2.6.1,<3.0.0)
|
23
24
|
Requires-Dist: jinja2 (>=3.1.4,<4.0.0)
|
24
25
|
Requires-Dist: lark (>=1.2.2,<2.0.0)
|
25
26
|
Requires-Dist: latexbuild (>=0.2.2,<0.3.0)
|
@@ -33,7 +34,6 @@ Requires-Dist: pydantic (==2.8.2)
|
|
33
34
|
Requires-Dist: pydantic-xml[lxml] (>=2.11.0,<3.0.0)
|
34
35
|
Requires-Dist: pypandoc (>=1.15,<2.0)
|
35
36
|
Requires-Dist: pyte (>=0.8.2,<0.9.0)
|
36
|
-
Requires-Dist: python-iso639 (>=2024.4.27,<2025.0.0)
|
37
37
|
Requires-Dist: pyyaml (>=6.0.1,<7.0.0)
|
38
38
|
Requires-Dist: questionary (>=2.1.0,<3.0.0)
|
39
39
|
Requires-Dist: requests (>=2.32.3,<3.0.0)
|