rbx.cp 0.11.2__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/builder.py +3 -3
- rbx/box/cli.py +9 -0
- rbx/box/contest/build_contest_statements.py +5 -6
- rbx/box/contest/statements.py +0 -1
- rbx/box/generators.py +93 -23
- rbx/box/header.py +5 -0
- rbx/box/lang.py +25 -12
- rbx/box/package.py +56 -4
- 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 +34 -14
- rbx/box/packaging/polygon/xml_schema.py +15 -6
- rbx/box/schema.py +3 -3
- rbx/box/solutions.py +8 -12
- rbx/box/statements/build_statements.py +0 -1
- rbx/box/statements/latex.py +11 -0
- rbx/box/stresses.py +1 -1
- rbx/box/tooling/converter.py +76 -0
- rbx/box/tooling/main.py +54 -1
- rbx/grading/caching.py +1 -0
- rbx/grading/judge/sandbox.py +1 -0
- rbx/grading/steps.py +1 -0
- rbx/resources/presets/default/contest/.gitignore +15 -0
- rbx/resources/presets/default/contest/contest.rbx.yml +2 -2
- rbx/resources/presets/default/problem/.gitignore +15 -0
- 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/resources/presets/default/problem/validator.cpp +2 -1
- rbx/resources/presets/default/shared/icpc.sty +1 -1
- rbx/utils.py +13 -0
- {rbx_cp-0.11.2.dist-info → rbx_cp-0.13.2.dist-info}/METADATA +2 -2
- {rbx_cp-0.11.2.dist-info → rbx_cp-0.13.2.dist-info}/RECORD +40 -37
- {rbx_cp-0.11.2.dist-info → rbx_cp-0.13.2.dist-info}/LICENSE +0 -0
- {rbx_cp-0.11.2.dist-info → rbx_cp-0.13.2.dist-info}/WHEEL +0 -0
- {rbx_cp-0.11.2.dist-info → rbx_cp-0.13.2.dist-info}/entry_points.txt +0 -0
rbx/box/packaging/main.py
CHANGED
@@ -1,72 +1,16 @@
|
|
1
|
-
import
|
2
|
-
import tempfile
|
3
|
-
from typing import Type
|
1
|
+
from typing import Optional
|
4
2
|
|
5
3
|
import syncer
|
6
4
|
import typer
|
7
5
|
|
8
|
-
from rbx import annotations
|
9
|
-
from rbx.box import environment,
|
10
|
-
from rbx.box.formatting import href
|
6
|
+
from rbx import annotations
|
7
|
+
from rbx.box import environment, package
|
11
8
|
from rbx.box.naming import get_problem_name_with_contest_info
|
12
|
-
from rbx.box.
|
13
|
-
from rbx.box.packaging.packager import BasePackager, BuiltStatement
|
14
|
-
from rbx.box.statements.build_statements import build_statement
|
9
|
+
from rbx.box.packaging.packager import run_packager
|
15
10
|
|
16
11
|
app = typer.Typer(no_args_is_help=True, cls=annotations.AliasGroup)
|
17
12
|
|
18
13
|
|
19
|
-
async def run_packager(
|
20
|
-
packager_cls: Type[BasePackager],
|
21
|
-
verification: environment.VerificationParam,
|
22
|
-
**kwargs,
|
23
|
-
) -> pathlib.Path:
|
24
|
-
from rbx.box import builder
|
25
|
-
|
26
|
-
header.generate_header()
|
27
|
-
|
28
|
-
if not await builder.verify(verification=verification):
|
29
|
-
console.console.print(
|
30
|
-
'[error]Build or verification failed, check the report.[/error]'
|
31
|
-
)
|
32
|
-
raise typer.Exit(1)
|
33
|
-
|
34
|
-
pkg = package.find_problem_package_or_die()
|
35
|
-
|
36
|
-
if pkg.type not in packager_cls.task_types():
|
37
|
-
console.console.print(
|
38
|
-
f'[error]Packager [item]{packager_cls.name()}[/item] does not support task type [item]{pkg.type}[/item].[/error]'
|
39
|
-
)
|
40
|
-
raise typer.Exit(1)
|
41
|
-
|
42
|
-
packager = packager_cls(**kwargs)
|
43
|
-
|
44
|
-
statement_types = packager.statement_types()
|
45
|
-
built_statements = []
|
46
|
-
|
47
|
-
for statement_type in statement_types:
|
48
|
-
languages = packager.languages()
|
49
|
-
for language in languages:
|
50
|
-
statement = packager.get_statement_for_language(language)
|
51
|
-
statement_path = build_statement(statement, pkg, statement_type)
|
52
|
-
built_statements.append(
|
53
|
-
BuiltStatement(statement, statement_path, statement_type)
|
54
|
-
)
|
55
|
-
|
56
|
-
console.console.print(f'Packaging problem for [item]{packager.name()}[/item]...')
|
57
|
-
|
58
|
-
with tempfile.TemporaryDirectory() as td:
|
59
|
-
result_path = packager.package(
|
60
|
-
get_build_path(), pathlib.Path(td), built_statements
|
61
|
-
)
|
62
|
-
|
63
|
-
console.console.print(
|
64
|
-
f'[success]Problem packaged for [item]{packager.name()}[/item]![/success]'
|
65
|
-
)
|
66
|
-
console.console.print(f'Package was saved at {href(result_path)}')
|
67
|
-
return result_path
|
68
|
-
|
69
|
-
|
70
14
|
@app.command('polygon', help='Build a package for Polygon.')
|
71
15
|
@package.within_problem
|
72
16
|
@syncer.sync
|
@@ -78,22 +22,31 @@ async def polygon(
|
|
78
22
|
'-u',
|
79
23
|
help='If set, will upload the package to Polygon.',
|
80
24
|
),
|
81
|
-
|
25
|
+
language: Optional[str] = typer.Option(
|
26
|
+
None,
|
27
|
+
'--language',
|
28
|
+
'-l',
|
29
|
+
help='If set, will use the given language as the main language.',
|
30
|
+
),
|
31
|
+
upload_as_english: bool = typer.Option(
|
82
32
|
False,
|
83
|
-
'--
|
84
|
-
help='If set, will
|
33
|
+
'--upload-as-english',
|
34
|
+
help='If set, will force the main statement to be uploaded in English.',
|
85
35
|
),
|
86
36
|
):
|
87
37
|
from rbx.box.packaging.polygon.packager import PolygonPackager
|
88
38
|
|
89
|
-
await run_packager(
|
39
|
+
await run_packager(
|
40
|
+
PolygonPackager, verification=verification, main_language=language
|
41
|
+
)
|
90
42
|
|
91
43
|
if upload:
|
92
44
|
from rbx.box.packaging.polygon.upload import upload_problem
|
93
45
|
|
94
46
|
await upload_problem(
|
95
47
|
name=get_problem_name_with_contest_info(),
|
96
|
-
|
48
|
+
main_language=language,
|
49
|
+
upload_as_english=upload_as_english,
|
97
50
|
)
|
98
51
|
|
99
52
|
|
rbx/box/packaging/packager.py
CHANGED
@@ -1,13 +1,20 @@
|
|
1
1
|
import dataclasses
|
2
2
|
import pathlib
|
3
|
+
import shutil
|
4
|
+
import tempfile
|
3
5
|
from abc import ABC, abstractmethod
|
4
|
-
from typing import List, Tuple
|
6
|
+
from typing import List, Tuple, Type
|
5
7
|
|
6
|
-
|
8
|
+
import typer
|
9
|
+
|
10
|
+
from rbx import console
|
11
|
+
from rbx.box import environment, header, naming, package
|
7
12
|
from rbx.box.contest import contest_package
|
8
13
|
from rbx.box.contest.schema import ContestProblem, ContestStatement
|
14
|
+
from rbx.box.formatting import href
|
9
15
|
from rbx.box.generators import get_all_built_testcases
|
10
16
|
from rbx.box.schema import Package, TaskType, Testcase, TestcaseGroup
|
17
|
+
from rbx.box.statements.build_statements import build_statement
|
11
18
|
from rbx.box.statements.schema import Statement, StatementType
|
12
19
|
|
13
20
|
|
@@ -127,3 +134,89 @@ class BaseContestPackager(ABC):
|
|
127
134
|
if statement.language == lang:
|
128
135
|
return statement
|
129
136
|
raise
|
137
|
+
|
138
|
+
|
139
|
+
class ContestZipper(BaseContestPackager):
|
140
|
+
def __init__(
|
141
|
+
self, filename: str, zip_inner: bool = False, prefer_shortname: bool = True
|
142
|
+
):
|
143
|
+
super().__init__()
|
144
|
+
self.zip_inner = zip_inner
|
145
|
+
self.filename = filename
|
146
|
+
self.prefer_shortname = prefer_shortname
|
147
|
+
|
148
|
+
def package(
|
149
|
+
self,
|
150
|
+
built_packages: List[BuiltProblemPackage],
|
151
|
+
build_path: pathlib.Path,
|
152
|
+
into_path: pathlib.Path,
|
153
|
+
built_statements: List[BuiltContestStatement],
|
154
|
+
) -> pathlib.Path:
|
155
|
+
for built_package in built_packages:
|
156
|
+
if self.prefer_shortname:
|
157
|
+
pkg_path = into_path / 'problems' / built_package.problem.short_name
|
158
|
+
else:
|
159
|
+
pkg_path = into_path / 'problems' / built_package.package.name
|
160
|
+
|
161
|
+
if self.zip_inner:
|
162
|
+
pkg_path.parent.mkdir(parents=True, exist_ok=True)
|
163
|
+
shutil.copy(built_package.path, pkg_path.with_suffix('.zip'))
|
164
|
+
else:
|
165
|
+
pkg_path.mkdir(parents=True, exist_ok=True)
|
166
|
+
shutil.unpack_archive(built_package.path, pkg_path, format='zip')
|
167
|
+
|
168
|
+
# Zip all.
|
169
|
+
shutil.make_archive(str(build_path / self.filename), 'zip', into_path)
|
170
|
+
|
171
|
+
return build_path / pathlib.Path(self.filename).with_suffix('.zip')
|
172
|
+
|
173
|
+
|
174
|
+
async def run_packager(
|
175
|
+
packager_cls: Type[BasePackager],
|
176
|
+
verification: environment.VerificationParam,
|
177
|
+
**kwargs,
|
178
|
+
) -> pathlib.Path:
|
179
|
+
from rbx.box import builder
|
180
|
+
|
181
|
+
header.generate_header()
|
182
|
+
|
183
|
+
if not await builder.verify(verification=verification):
|
184
|
+
console.console.print(
|
185
|
+
'[error]Build or verification failed, check the report.[/error]'
|
186
|
+
)
|
187
|
+
raise typer.Exit(1)
|
188
|
+
|
189
|
+
pkg = package.find_problem_package_or_die()
|
190
|
+
|
191
|
+
if pkg.type not in packager_cls.task_types():
|
192
|
+
console.console.print(
|
193
|
+
f'[error]Packager [item]{packager_cls.name()}[/item] does not support task type [item]{pkg.type}[/item].[/error]'
|
194
|
+
)
|
195
|
+
raise typer.Exit(1)
|
196
|
+
|
197
|
+
packager = packager_cls(**kwargs)
|
198
|
+
|
199
|
+
statement_types = packager.statement_types()
|
200
|
+
built_statements = []
|
201
|
+
|
202
|
+
for statement_type in statement_types:
|
203
|
+
languages = packager.languages()
|
204
|
+
for language in languages:
|
205
|
+
statement = packager.get_statement_for_language(language)
|
206
|
+
statement_path = build_statement(statement, pkg, statement_type)
|
207
|
+
built_statements.append(
|
208
|
+
BuiltStatement(statement, statement_path, statement_type)
|
209
|
+
)
|
210
|
+
|
211
|
+
console.console.print(f'Packaging problem for [item]{packager.name()}[/item]...')
|
212
|
+
|
213
|
+
with tempfile.TemporaryDirectory() as td:
|
214
|
+
result_path = packager.package(
|
215
|
+
package.get_build_path(), pathlib.Path(td), built_statements
|
216
|
+
)
|
217
|
+
|
218
|
+
console.console.print(
|
219
|
+
f'[success]Problem packaged for [item]{packager.name()}[/item]![/success]'
|
220
|
+
)
|
221
|
+
console.console.print(f'Package was saved at {href(result_path)}')
|
222
|
+
return result_path
|
@@ -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'
|
@@ -214,8 +214,7 @@ def _upload_testcases(problem: api.Problem):
|
|
214
214
|
|
215
215
|
def _upload_solutions(problem: api.Problem):
|
216
216
|
console.console.print('Uploading main solution...')
|
217
|
-
|
218
|
-
main_solution = pkg.solutions[0]
|
217
|
+
main_solution = package.get_main_solution()
|
219
218
|
if main_solution is None or main_solution.outcome != ExpectedOutcome.ACCEPTED:
|
220
219
|
return
|
221
220
|
problem.save_solution(
|
@@ -225,7 +224,7 @@ def _upload_solutions(problem: api.Problem):
|
|
225
224
|
tag=api.SolutionTag.MA,
|
226
225
|
)
|
227
226
|
|
228
|
-
for i, solution in enumerate(
|
227
|
+
for i, solution in enumerate(package.get_solutions()):
|
229
228
|
console.console.print(
|
230
229
|
f'Uploading solution [item]{solution.path.name}[/item] (tag: [item]{_get_solution_tag(solution, is_first=i == 0)}[/item])...'
|
231
230
|
)
|
@@ -296,19 +295,32 @@ def _upload_statement_resources(problem: api.Problem, statement: Statement):
|
|
296
295
|
)
|
297
296
|
|
298
297
|
|
299
|
-
def _upload_statement(
|
298
|
+
def _upload_statement(
|
299
|
+
problem: api.Problem, main_language: Optional[str], upload_as_english: bool = False
|
300
|
+
):
|
300
301
|
pkg = package.find_problem_package_or_die()
|
301
302
|
|
303
|
+
lang_list = []
|
302
304
|
languages = set()
|
303
305
|
for statement in pkg.expanded_statements:
|
304
306
|
if not is_valid_lang_code(statement.language):
|
305
307
|
continue
|
306
308
|
languages.add(statement.language)
|
307
|
-
|
309
|
+
lang_list.append(statement.language)
|
308
310
|
uploaded_languages = set()
|
309
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
|
+
|
310
322
|
# Prioritize English statements.
|
311
|
-
for language in
|
323
|
+
for language in lang_list:
|
312
324
|
statement = _get_statement_for_language(language)
|
313
325
|
if statement is None:
|
314
326
|
continue
|
@@ -318,15 +330,19 @@ def _upload_statement(problem: api.Problem, preserve_language: bool = False):
|
|
318
330
|
console.console.print(
|
319
331
|
f'Uploading statement for language [item]{language}[/item] (polygon language: [item]{statement_lang}[/item])...'
|
320
332
|
)
|
321
|
-
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'
|
322
343
|
if uploaded_language in uploaded_languages:
|
323
344
|
continue
|
324
345
|
uploaded_languages.add(uploaded_language)
|
325
|
-
if not preserve_language and statement_lang != 'english':
|
326
|
-
console.console.print(
|
327
|
-
'[warning]By default, Polygon statements are uploaded in English.\n'
|
328
|
-
'If you want to preserve the original language of your statement, use [item]--preserve-language[/item].'
|
329
|
-
)
|
330
346
|
blocks = _get_statement_blocks(statement)
|
331
347
|
polygon_statement = api.Statement(
|
332
348
|
encoding='utf-8',
|
@@ -351,7 +367,9 @@ def _normalize_problem_name(name: str) -> str:
|
|
351
367
|
return name.replace(' ', '-').replace('_', '-').lower()
|
352
368
|
|
353
369
|
|
354
|
-
async def upload_problem(
|
370
|
+
async def upload_problem(
|
371
|
+
name: str, main_language: Optional[str], upload_as_english: bool = False
|
372
|
+
):
|
355
373
|
pkg = package.find_problem_package_or_die()
|
356
374
|
name = _normalize_problem_name(name)
|
357
375
|
problem = _find_or_create_problem(name)
|
@@ -370,7 +388,9 @@ async def upload_problem(name: str, preserve_language: bool = False):
|
|
370
388
|
|
371
389
|
_upload_solutions(problem)
|
372
390
|
_upload_testcases(problem)
|
373
|
-
_upload_statement(
|
391
|
+
_upload_statement(
|
392
|
+
problem, main_language=main_language, upload_as_english=upload_as_english
|
393
|
+
)
|
374
394
|
|
375
395
|
# Commit.
|
376
396
|
console.console.print('Committing changes...')
|