rbx.cp 0.5.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.
Files changed (164) hide show
  1. rbx/__init__.py +0 -0
  2. rbx/annotations.py +127 -0
  3. rbx/autoenum.py +333 -0
  4. rbx/box/__init__.py +0 -0
  5. rbx/box/builder.py +77 -0
  6. rbx/box/cd.py +37 -0
  7. rbx/box/checkers.py +134 -0
  8. rbx/box/code.py +185 -0
  9. rbx/box/compile.py +56 -0
  10. rbx/box/conftest.py +42 -0
  11. rbx/box/contest/__init__.py +0 -0
  12. rbx/box/contest/build_contest_statements.py +347 -0
  13. rbx/box/contest/contest_package.py +76 -0
  14. rbx/box/contest/contest_utils.py +20 -0
  15. rbx/box/contest/main.py +179 -0
  16. rbx/box/contest/schema.py +155 -0
  17. rbx/box/contest/statements.py +82 -0
  18. rbx/box/creation.py +72 -0
  19. rbx/box/download.py +64 -0
  20. rbx/box/environment.py +345 -0
  21. rbx/box/extensions.py +26 -0
  22. rbx/box/generators.py +478 -0
  23. rbx/box/generators_test.py +63 -0
  24. rbx/box/main.py +449 -0
  25. rbx/box/package.py +316 -0
  26. rbx/box/packaging/boca/extension.py +27 -0
  27. rbx/box/packaging/boca/packager.py +245 -0
  28. rbx/box/packaging/contest_main.py +82 -0
  29. rbx/box/packaging/main.py +68 -0
  30. rbx/box/packaging/packager.py +117 -0
  31. rbx/box/packaging/polygon/packager.py +320 -0
  32. rbx/box/packaging/polygon/test.py +81 -0
  33. rbx/box/packaging/polygon/xml_schema.py +106 -0
  34. rbx/box/presets/__init__.py +503 -0
  35. rbx/box/presets/fetch.py +70 -0
  36. rbx/box/presets/lock_schema.py +20 -0
  37. rbx/box/presets/schema.py +59 -0
  38. rbx/box/schema.py +394 -0
  39. rbx/box/solutions.py +792 -0
  40. rbx/box/solutions_test.py +41 -0
  41. rbx/box/statements/__init__.py +0 -0
  42. rbx/box/statements/build_statements.py +359 -0
  43. rbx/box/statements/builders.py +375 -0
  44. rbx/box/statements/joiners.py +113 -0
  45. rbx/box/statements/latex.py +47 -0
  46. rbx/box/statements/latex_jinja.py +214 -0
  47. rbx/box/statements/schema.py +138 -0
  48. rbx/box/stresses.py +292 -0
  49. rbx/box/stressing/__init__.py +0 -0
  50. rbx/box/stressing/finder_parser.py +359 -0
  51. rbx/box/stressing/generator_parser.py +258 -0
  52. rbx/box/testcases.py +54 -0
  53. rbx/box/ui/__init__.py +0 -0
  54. rbx/box/ui/captured_log.py +372 -0
  55. rbx/box/ui/css/app.tcss +48 -0
  56. rbx/box/ui/main.py +38 -0
  57. rbx/box/ui/run.py +209 -0
  58. rbx/box/validators.py +245 -0
  59. rbx/box/validators_test.py +15 -0
  60. rbx/checker.py +128 -0
  61. rbx/clone.py +197 -0
  62. rbx/config.py +271 -0
  63. rbx/conftest.py +38 -0
  64. rbx/console.py +27 -0
  65. rbx/create.py +37 -0
  66. rbx/edit.py +24 -0
  67. rbx/grading/__init__.py +0 -0
  68. rbx/grading/caching.py +356 -0
  69. rbx/grading/conftest.py +33 -0
  70. rbx/grading/judge/__init__.py +0 -0
  71. rbx/grading/judge/cacher.py +503 -0
  72. rbx/grading/judge/digester.py +35 -0
  73. rbx/grading/judge/sandbox.py +748 -0
  74. rbx/grading/judge/sandboxes/__init__.py +0 -0
  75. rbx/grading/judge/sandboxes/isolate.py +683 -0
  76. rbx/grading/judge/sandboxes/stupid_sandbox.py +310 -0
  77. rbx/grading/judge/sandboxes/timeit.py +217 -0
  78. rbx/grading/judge/storage.py +284 -0
  79. rbx/grading/judge/test.py +38 -0
  80. rbx/grading/judge/testiso.py +54 -0
  81. rbx/grading/steps.py +522 -0
  82. rbx/grading/steps_with_caching.py +59 -0
  83. rbx/grading/steps_with_caching_run_test.py +429 -0
  84. rbx/grading_utils.py +148 -0
  85. rbx/hydration.py +101 -0
  86. rbx/main.py +122 -0
  87. rbx/metadata.py +105 -0
  88. rbx/providers/__init__.py +43 -0
  89. rbx/providers/codeforces.py +73 -0
  90. rbx/providers/provider.py +26 -0
  91. rbx/resources/checkers/boilerplate.cpp +20 -0
  92. rbx/resources/default_config.json +48 -0
  93. rbx/resources/envs/default.rbx.yml +37 -0
  94. rbx/resources/envs/isolate.rbx.yml +37 -0
  95. rbx/resources/packagers/boca/checker.sh +43 -0
  96. rbx/resources/packagers/boca/compare +53 -0
  97. rbx/resources/packagers/boca/compile/c +172 -0
  98. rbx/resources/packagers/boca/compile/cc +173 -0
  99. rbx/resources/packagers/boca/compile/cpp +172 -0
  100. rbx/resources/packagers/boca/compile/java +194 -0
  101. rbx/resources/packagers/boca/compile/kt +155 -0
  102. rbx/resources/packagers/boca/compile/pas +172 -0
  103. rbx/resources/packagers/boca/compile/py2 +173 -0
  104. rbx/resources/packagers/boca/compile/py3 +173 -0
  105. rbx/resources/packagers/boca/run/c +128 -0
  106. rbx/resources/packagers/boca/run/cc +128 -0
  107. rbx/resources/packagers/boca/run/cpp +128 -0
  108. rbx/resources/packagers/boca/run/java +194 -0
  109. rbx/resources/packagers/boca/run/kt +159 -0
  110. rbx/resources/packagers/boca/run/py2 +166 -0
  111. rbx/resources/packagers/boca/run/py3 +166 -0
  112. rbx/resources/presets/default/contest/contest.rbx.yml +14 -0
  113. rbx/resources/presets/default/contest/statement/contest.rbx.tex +97 -0
  114. rbx/resources/presets/default/contest/statement/olymp.sty +250 -0
  115. rbx/resources/presets/default/contest/statement/template.rbx.tex +42 -0
  116. rbx/resources/presets/default/preset.rbx.yml +12 -0
  117. rbx/resources/presets/default/problem/.gitignore +6 -0
  118. rbx/resources/presets/default/problem/gen.cpp +9 -0
  119. rbx/resources/presets/default/problem/problem.rbx.yml +44 -0
  120. rbx/resources/presets/default/problem/random.py +3 -0
  121. rbx/resources/presets/default/problem/random.txt +2 -0
  122. rbx/resources/presets/default/problem/sols/main.cpp +9 -0
  123. rbx/resources/presets/default/problem/sols/slow.cpp +15 -0
  124. rbx/resources/presets/default/problem/sols/wa.cpp +9 -0
  125. rbx/resources/presets/default/problem/statement/olymp.sty +250 -0
  126. rbx/resources/presets/default/problem/statement/projecao.png +0 -0
  127. rbx/resources/presets/default/problem/statement/statement.rbx.tex +18 -0
  128. rbx/resources/presets/default/problem/statement/template.rbx.tex +89 -0
  129. rbx/resources/presets/default/problem/tests/samples/000.in +1 -0
  130. rbx/resources/presets/default/problem/tests/samples/001.in +1 -0
  131. rbx/resources/presets/default/problem/validator.cpp +16 -0
  132. rbx/resources/presets/default/problem/wcmp.cpp +34 -0
  133. rbx/resources/templates/template.cpp +19 -0
  134. rbx/run.py +45 -0
  135. rbx/schema.py +64 -0
  136. rbx/submit.py +61 -0
  137. rbx/submitors/__init__.py +18 -0
  138. rbx/submitors/codeforces.py +120 -0
  139. rbx/submitors/submitor.py +25 -0
  140. rbx/test.py +347 -0
  141. rbx/testcase.py +70 -0
  142. rbx/testcase_rendering.py +79 -0
  143. rbx/testdata/box1/gen1.cpp +7 -0
  144. rbx/testdata/box1/gen2.cpp +9 -0
  145. rbx/testdata/box1/genScript.py +2 -0
  146. rbx/testdata/box1/hard-tle.sol.cpp +26 -0
  147. rbx/testdata/box1/ole.cpp +17 -0
  148. rbx/testdata/box1/problem.rbx.yml +39 -0
  149. rbx/testdata/box1/re.sol.cpp +23 -0
  150. rbx/testdata/box1/sol.cpp +22 -0
  151. rbx/testdata/box1/tests/1.in +1 -0
  152. rbx/testdata/box1/tle-and-incorrect.sol.cpp +33 -0
  153. rbx/testdata/box1/tle.sol.cpp +35 -0
  154. rbx/testdata/box1/validator.cpp +11 -0
  155. rbx/testdata/box1/wa.sol.cpp +22 -0
  156. rbx/testdata/caching/executable.py +1 -0
  157. rbx/testdata/compatible +0 -0
  158. rbx/testing_utils.py +65 -0
  159. rbx/utils.py +162 -0
  160. rbx_cp-0.5.0.dist-info/LICENSE +201 -0
  161. rbx_cp-0.5.0.dist-info/METADATA +89 -0
  162. rbx_cp-0.5.0.dist-info/RECORD +164 -0
  163. rbx_cp-0.5.0.dist-info/WHEEL +4 -0
  164. rbx_cp-0.5.0.dist-info/entry_points.txt +4 -0
rbx/box/package.py ADDED
@@ -0,0 +1,316 @@
1
+ import functools
2
+ import pathlib
3
+ from typing import Dict, List, Optional, Tuple
4
+
5
+ import typer
6
+
7
+ from rbx import config, console, utils
8
+ from rbx.box import environment
9
+ from rbx.box.environment import get_sandbox_type
10
+ from rbx.box.presets import get_installed_preset_or_null, get_preset_lock
11
+ from rbx.box.schema import (
12
+ CodeItem,
13
+ ExpectedOutcome,
14
+ Generator,
15
+ Package,
16
+ Solution,
17
+ Stress,
18
+ TestcaseGroup,
19
+ TestcaseSubgroup,
20
+ )
21
+ from rbx.config import get_builtin_checker
22
+ from rbx.grading.caching import DependencyCache
23
+ from rbx.grading.judge.cacher import FileCacher
24
+ from rbx.grading.judge.sandbox import SandboxBase
25
+ from rbx.grading.judge.storage import FilesystemStorage, Storage
26
+
27
+ YAML_NAME = 'problem.rbx.yml'
28
+ _DEFAULT_CHECKER = 'wcmp.cpp'
29
+ TEMP_DIR = None
30
+
31
+
32
+ def warn_preset_deactivated(root: pathlib.Path = pathlib.Path()):
33
+ preset_lock = get_preset_lock(root)
34
+ if preset_lock is None:
35
+ return
36
+
37
+ preset = get_installed_preset_or_null(preset_lock.preset_name)
38
+ if preset is None:
39
+ console.console.print(
40
+ f'[warning]WARNING: [item]{preset_lock.preset_name}[/item] is not installed. '
41
+ 'Run [item]rbx presets sync && rbx activate[/item] to install and activate this preset.'
42
+ )
43
+ console.console.print()
44
+ return
45
+
46
+ if preset.env is not None and (
47
+ not environment.get_environment_path(preset.name).is_file()
48
+ or config.get_config().boxEnvironment != preset.name
49
+ ):
50
+ console.console.print(
51
+ '[warning]WARNING: This package uses a preset that configures a custom environment, '
52
+ f' but instead you are using the environment [item]{config.get_config().boxEnvironment}[/item]. '
53
+ 'Run [item]rbx activate[/item] to use the environment configured by your preset.'
54
+ )
55
+ console.console.print()
56
+ return
57
+
58
+
59
+ @functools.cache
60
+ def find_problem_yaml(root: pathlib.Path = pathlib.Path()) -> Optional[pathlib.Path]:
61
+ root = root.resolve()
62
+ problem_yaml_path = root / YAML_NAME
63
+ while root != pathlib.PosixPath('/') and not problem_yaml_path.is_file():
64
+ root = root.parent
65
+ problem_yaml_path = root / YAML_NAME
66
+ if not problem_yaml_path.is_file():
67
+ return None
68
+ warn_preset_deactivated(root)
69
+ return problem_yaml_path
70
+
71
+
72
+ @functools.cache
73
+ def find_problem_package(root: pathlib.Path = pathlib.Path()) -> Optional[Package]:
74
+ problem_yaml_path = find_problem_yaml(root)
75
+ if not problem_yaml_path:
76
+ return None
77
+ return utils.model_from_yaml(Package, problem_yaml_path.read_text())
78
+
79
+
80
+ def find_problem_package_or_die(root: pathlib.Path = pathlib.Path()) -> Package:
81
+ package = find_problem_package(root)
82
+ if package is None:
83
+ console.console.print(f'[error]Problem not found in {root.absolute()}[/error]')
84
+ raise typer.Exit(1)
85
+ return package
86
+
87
+
88
+ def find_problem(root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
89
+ found = find_problem_yaml(root)
90
+ if found is None:
91
+ console.console.print(f'[error]Problem not found in {root.absolute()}[/error]')
92
+ raise typer.Exit(1)
93
+ return found.parent
94
+
95
+
96
+ def within_problem(func):
97
+ @functools.wraps(func)
98
+ def wrapper(*args, **kwargs):
99
+ with utils.new_cd(find_problem()):
100
+ return func(*args, **kwargs)
101
+
102
+ return wrapper
103
+
104
+
105
+ def save_package(
106
+ package: Optional[Package] = None, root: pathlib.Path = pathlib.Path()
107
+ ) -> None:
108
+ package = package or find_problem_package_or_die(root)
109
+ problem_yaml_path = find_problem_yaml(root)
110
+ if not problem_yaml_path:
111
+ console.console.print(f'[error]Problem not found in {root.absolute()}[/error]')
112
+ raise typer.Exit(1)
113
+ problem_yaml_path.write_text(utils.model_to_yaml(package))
114
+
115
+
116
+ @functools.cache
117
+ def get_problem_cache_dir(root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
118
+ cache_dir = find_problem(root) / '.box'
119
+ cache_dir.mkdir(parents=True, exist_ok=True)
120
+ return cache_dir
121
+
122
+
123
+ @functools.cache
124
+ def get_problem_storage_dir(root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
125
+ storage_dir = get_problem_cache_dir(root) / '.storage'
126
+ storage_dir.mkdir(parents=True, exist_ok=True)
127
+ return storage_dir
128
+
129
+
130
+ def get_problem_runs_dir(root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
131
+ runs_dir = get_problem_cache_dir(root) / 'runs'
132
+ runs_dir.mkdir(parents=True, exist_ok=True)
133
+ return runs_dir
134
+
135
+
136
+ @functools.cache
137
+ def get_cache_storage(root: pathlib.Path = pathlib.Path()) -> Storage:
138
+ return FilesystemStorage(get_problem_storage_dir(root))
139
+
140
+
141
+ @functools.cache
142
+ def get_dependency_cache(root: pathlib.Path = pathlib.Path()) -> DependencyCache:
143
+ return DependencyCache(get_problem_cache_dir(root), get_cache_storage(root))
144
+
145
+
146
+ @functools.cache
147
+ def get_file_cacher(root: pathlib.Path = pathlib.Path()) -> FileCacher:
148
+ return FileCacher(get_cache_storage(root))
149
+
150
+
151
+ @functools.cache
152
+ def get_digest_as_string(
153
+ digest: str, root: pathlib.Path = pathlib.Path()
154
+ ) -> Optional[str]:
155
+ cacher = get_file_cacher(root)
156
+ try:
157
+ content = cacher.get_file_content(digest)
158
+ return content.decode()
159
+ except KeyError:
160
+ return None
161
+
162
+
163
+ def get_new_sandbox(root: pathlib.Path = pathlib.Path()) -> SandboxBase:
164
+ return get_sandbox_type()(file_cacher=get_file_cacher(root), temp_dir=TEMP_DIR)
165
+
166
+
167
+ @functools.cache
168
+ def get_singleton_sandbox(root: pathlib.Path = pathlib.Path()) -> SandboxBase:
169
+ return get_new_sandbox(root)
170
+
171
+
172
+ @functools.cache
173
+ def get_build_path(root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
174
+ return find_problem(root) / 'build'
175
+
176
+
177
+ @functools.cache
178
+ def get_build_tests_path(root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
179
+ return get_build_path(root) / 'tests'
180
+
181
+
182
+ @functools.cache
183
+ def get_build_testgroup_path(
184
+ group: str, root: pathlib.Path = pathlib.Path()
185
+ ) -> pathlib.Path:
186
+ res = get_build_tests_path(root) / group
187
+ res.mkdir(exist_ok=True, parents=True)
188
+ return res
189
+
190
+
191
+ @functools.cache
192
+ def get_generator(name: str, root: pathlib.Path = pathlib.Path()) -> Generator:
193
+ package = find_problem_package_or_die(root)
194
+ for generator in package.generators:
195
+ if generator.name == name:
196
+ return generator
197
+ console.console.print(f'[error]Generator [item]{name}[/item] not found[/error]')
198
+ raise typer.Exit(1)
199
+
200
+
201
+ @functools.cache
202
+ def get_validator(root: pathlib.Path = pathlib.Path()) -> CodeItem:
203
+ package = find_problem_package_or_die(root)
204
+ if package.validator is None:
205
+ console.console.print(
206
+ '[error]Problem does not have a validator configured.[/error]'
207
+ )
208
+ raise typer.Exit(1)
209
+ return package.validator
210
+
211
+
212
+ @functools.cache
213
+ def get_checker(root: pathlib.Path = pathlib.Path()) -> CodeItem:
214
+ package = find_problem_package_or_die(root)
215
+
216
+ return package.checker or CodeItem(
217
+ path=get_builtin_checker(_DEFAULT_CHECKER).absolute()
218
+ )
219
+
220
+
221
+ @functools.cache
222
+ def get_solutions(root: pathlib.Path = pathlib.Path()) -> List[Solution]:
223
+ package = find_problem_package_or_die(root)
224
+ return package.solutions
225
+
226
+
227
+ @functools.cache
228
+ def get_main_solution(root: pathlib.Path = pathlib.Path()) -> Optional[Solution]:
229
+ for solution in get_solutions(root):
230
+ if solution.outcome == ExpectedOutcome.ACCEPTED:
231
+ return solution
232
+ return None
233
+
234
+
235
+ @functools.cache
236
+ def get_solution(name: str, root: pathlib.Path = pathlib.Path()) -> Solution:
237
+ for solution in get_solutions(root):
238
+ if str(solution.path) == name:
239
+ return solution
240
+ console.console.print(f'[error]Solution [item]{name}[/item] not found[/error]')
241
+ raise typer.Exit(1)
242
+
243
+
244
+ @functools.cache
245
+ def get_solution_or_nil(
246
+ name: str, root: pathlib.Path = pathlib.Path()
247
+ ) -> Optional[Solution]:
248
+ for solution in get_solutions(root):
249
+ if str(solution.path) == name:
250
+ return solution
251
+ return None
252
+
253
+
254
+ @functools.cache
255
+ def get_stress(name: str, root: pathlib.Path = pathlib.Path()) -> Stress:
256
+ pkg = find_problem_package_or_die(root)
257
+ for stress in pkg.stresses:
258
+ if stress.name == name:
259
+ return stress
260
+ console.console.print(f'[error]Stress [item]{name}[/item] not found[/error]')
261
+ raise typer.Exit(1)
262
+
263
+
264
+ @functools.cache
265
+ def get_testgroup(name: str, root: pathlib.Path = pathlib.Path()) -> TestcaseGroup:
266
+ pkg = find_problem_package_or_die(root)
267
+ for testgroup in pkg.testcases:
268
+ if testgroup.name == name:
269
+ return testgroup
270
+ console.console.print(f'[error]Test group [item]{name}[/item] not found[/error]')
271
+ raise typer.Exit(1)
272
+
273
+
274
+ @functools.cache
275
+ def get_test_groups_by_name(
276
+ root: pathlib.Path = pathlib.Path(),
277
+ ) -> Dict[str, TestcaseSubgroup]:
278
+ pkg = find_problem_package_or_die(root)
279
+ res = {}
280
+
281
+ for testgroup in pkg.testcases:
282
+ res[testgroup.name] = testgroup
283
+ for subgroup in testgroup.subgroups:
284
+ res[f'{testgroup.name}.{subgroup.name}'] = subgroup
285
+
286
+ return res
287
+
288
+
289
+ # Return each compilation file and to where it should be moved inside
290
+ # the sandbox.
291
+ def get_compilation_files(code: CodeItem) -> List[Tuple[pathlib.Path, pathlib.Path]]:
292
+ code_dir = code.path.parent.resolve()
293
+
294
+ res = []
295
+ for compilation_file in code.compilationFiles or []:
296
+ compilation_file_path = pathlib.Path(compilation_file).resolve()
297
+ if not compilation_file_path.is_file():
298
+ console.console.print(
299
+ f'[error]Compilation file [item]{compilation_file}[/item] for '
300
+ f'code [item]{code.path}[/item] does not exist.[/error]',
301
+ )
302
+ raise typer.Exit(1)
303
+ if not compilation_file_path.is_relative_to(code_dir):
304
+ console.console.print(
305
+ f'[error]Compilation file [item]{compilation_file}[/item] for '
306
+ f"code [item]{code.path}[/item] is not under the code's folder.[/error]",
307
+ )
308
+ raise typer.Exit(1)
309
+
310
+ res.append(
311
+ (
312
+ pathlib.Path(compilation_file),
313
+ compilation_file_path.relative_to(code_dir),
314
+ )
315
+ )
316
+ return res
@@ -0,0 +1,27 @@
1
+ import typing
2
+
3
+ from pydantic import BaseModel
4
+
5
+ BocaLanguage = typing.Literal['c', 'cpp', 'cc', 'kt', 'java', 'py2', 'py3']
6
+
7
+ _MAX_REP_ERROR = 0.2 # 20% error allowed in time limit when adding reps
8
+
9
+
10
+ class BocaExtension(BaseModel):
11
+ languages: typing.List[BocaLanguage] = list(typing.get_args(BocaLanguage))
12
+ flags: typing.Dict[BocaLanguage, str] = {}
13
+ maximumTimeError: float = _MAX_REP_ERROR
14
+
15
+ def flags_with_defaults(self) -> typing.Dict[BocaLanguage, str]:
16
+ res: typing.Dict[BocaLanguage, str] = {
17
+ 'c': '-std=gnu11 -O2 -static -lm',
18
+ 'cpp': '-O2 -static -lm',
19
+ 'cc': '-std=c++20 -O2 -static -lm',
20
+ }
21
+ res.update(self.flags)
22
+ return res
23
+
24
+
25
+ class BocaLanguageExtension(BaseModel):
26
+ # BocaLanguage this rbx language matches with.
27
+ bocaLanguage: typing.Optional[str] = None
@@ -0,0 +1,245 @@
1
+ import pathlib
2
+ import shutil
3
+ from math import fabs
4
+ from typing import List
5
+
6
+ import typer
7
+
8
+ from rbx import console
9
+ from rbx.box import package
10
+ from rbx.box.environment import get_extension_or_default
11
+ from rbx.box.packaging.boca.extension import BocaExtension, BocaLanguage
12
+ from rbx.box.packaging.packager import BasePackager, BuiltStatement
13
+ from rbx.box.statements.schema import Statement
14
+ from rbx.config import get_default_app_path, get_testlib
15
+
16
+ _MAX_REP_TIME = (
17
+ 7 # TL to allow for additional rounding reps should be < _MAX_REP_TIME in seconds
18
+ )
19
+ _MAX_REPS = 10 # Maximum number of reps to add
20
+
21
+
22
+ def test_time(time):
23
+ return max(1, round(time))
24
+
25
+
26
+ class BocaPackager(BasePackager):
27
+ def _get_main_statement(self) -> Statement:
28
+ pkg = package.find_problem_package_or_die()
29
+
30
+ if not pkg.statements:
31
+ console.console.print('[error]No statements found.[/error]')
32
+ raise typer.Exit(1)
33
+
34
+ return pkg.statements[0]
35
+
36
+ def _get_main_built_statement(
37
+ self, built_statements: List[BuiltStatement]
38
+ ) -> BuiltStatement:
39
+ statement = self._get_main_statement()
40
+ for built_statement in built_statements:
41
+ if built_statement.statement == statement:
42
+ return built_statement
43
+
44
+ console.console.print(
45
+ '[error]Main statement not found among built statements.[/error]'
46
+ )
47
+ raise typer.Exit(1)
48
+
49
+ def _get_problem_name(self) -> str:
50
+ pkg = package.find_problem_package_or_die()
51
+ return pkg.name
52
+
53
+ def _get_problem_info(self) -> str:
54
+ pkg = package.find_problem_package_or_die()
55
+ statement = self._get_main_statement()
56
+ return (
57
+ f'basename={pkg.name}\n'
58
+ f'fullname={statement.title}\n'
59
+ f'descfile={self._get_problem_name()}.pdf\n'
60
+ )
61
+
62
+ def _get_pkg_timelimit(self, language: BocaLanguage) -> int:
63
+ pkg = package.find_problem_package_or_die()
64
+ return pkg.timelimit_for_language(language)
65
+
66
+ def _get_pkg_memorylimit(self, language: BocaLanguage) -> int:
67
+ pkg = package.find_problem_package_or_die()
68
+ return pkg.memorylimit_for_language(language)
69
+
70
+ def _get_number_of_runs(self, language: BocaLanguage) -> int:
71
+ pkg = package.find_problem_package_or_die()
72
+ extension = get_extension_or_default('boca', BocaExtension)
73
+ pkg_timelimit = self._get_pkg_timelimit(language)
74
+ time = pkg_timelimit / 1000 # convert to seconds
75
+
76
+ if time >= _MAX_REP_TIME:
77
+ return 1
78
+
79
+ def rounding_error(time):
80
+ return fabs(time - test_time(time))
81
+
82
+ def error_percentage(time, runs):
83
+ return rounding_error(time * runs) / (time * runs)
84
+
85
+ if error_percentage(time, 1) < 1e-6:
86
+ return 1
87
+
88
+ for i in range(1, _MAX_REPS + 1):
89
+ if error_percentage(time, i) <= extension.maximumTimeError:
90
+ console.console.print(
91
+ f'[warning]Using {i} run(s) to define integer TL for BOCA when using language [item]{language}[/item] '
92
+ f'(original TL is {pkg_timelimit}ms, new TL is {test_time(time * i) * 1000}ms).[/warning]'
93
+ )
94
+ return i
95
+
96
+ percent_str = f'{round(extension.maximumTimeError * 100)}%'
97
+ console.console.print(
98
+ f'[error]Error while defining limits for problem [item]{pkg.name}[/item], language [item]{language}[/item].[/error]'
99
+ )
100
+ console.console.print(
101
+ f'[error]Introducing an error of less than {percent_str} in the TL in less than '
102
+ f'{_MAX_REPS} runs is not possible.[/error]'
103
+ )
104
+ console.console.print(
105
+ f'[error]Original TL for [item]{language}[/item] is {pkg_timelimit}ms, please review it.[/error]'
106
+ )
107
+ raise typer.Exit(1)
108
+
109
+ def _get_limits(self, language: BocaLanguage) -> str:
110
+ pkg = package.find_problem_package_or_die()
111
+ no_of_runs = self._get_number_of_runs(language)
112
+ return (
113
+ '#!/bin/bash\n'
114
+ f'echo {test_time(self._get_pkg_timelimit(language) / 1000 * no_of_runs)}\n'
115
+ f'echo {no_of_runs}\n'
116
+ f'echo {self._get_pkg_memorylimit(language)}\n'
117
+ f'echo {pkg.outputLimit}\n'
118
+ f'exit 0\n'
119
+ )
120
+
121
+ def _get_compare(self) -> str:
122
+ compare_path = get_default_app_path() / 'packagers' / 'boca' / 'compare'
123
+ if not compare_path.exists():
124
+ console.console.print(
125
+ '[error]BOCA template compare script not found.[/error]'
126
+ )
127
+ raise typer.Exit(1)
128
+ return compare_path.read_text()
129
+
130
+ def _get_checker(self) -> str:
131
+ checker_path = get_default_app_path() / 'packagers' / 'boca' / 'checker.sh'
132
+ if not checker_path.exists():
133
+ console.console.print(
134
+ '[error]BOCA template checker script not found.[/error]'
135
+ )
136
+ raise typer.Exit(1)
137
+ checker_text = checker_path.read_text()
138
+ testlib = get_testlib().read_text()
139
+ checker = package.get_checker().path.read_text()
140
+ return checker_text.replace('{{testlib_content}}', testlib).replace(
141
+ '{{checker_content}}', checker
142
+ )
143
+
144
+ def _get_compile(self, language: BocaLanguage) -> str:
145
+ extension = get_extension_or_default('boca', BocaExtension)
146
+
147
+ compile_path = (
148
+ get_default_app_path() / 'packagers' / 'boca' / 'compile' / language
149
+ )
150
+ if not compile_path.is_file():
151
+ console.console.print(
152
+ f'[error]Compile script for language [item]{language}[/item] not found.[/error]'
153
+ )
154
+ raise typer.Exit(1)
155
+
156
+ compile_text = compile_path.read_text()
157
+
158
+ assert 'umask 0022' in compile_text
159
+ compile_text = compile_text.replace(
160
+ 'umask 0022', 'umask 0022\n\n' + self._get_checker()
161
+ )
162
+
163
+ flags = extension.flags_with_defaults()
164
+ if language in flags:
165
+ compile_text = compile_text.replace('{{rbxFlags}}', flags[language])
166
+ return compile_text
167
+
168
+ def name(self) -> str:
169
+ return 'boca'
170
+
171
+ def package(
172
+ self,
173
+ build_path: pathlib.Path,
174
+ into_path: pathlib.Path,
175
+ built_statements: List[BuiltStatement],
176
+ ) -> pathlib.Path:
177
+ extension = get_extension_or_default('boca', BocaExtension)
178
+
179
+ # Prepare limits
180
+ limits_path = into_path / 'limits'
181
+ limits_path.mkdir(parents=True, exist_ok=True)
182
+ for language in extension.languages:
183
+ (limits_path / language).write_text(self._get_limits(language))
184
+
185
+ # Prepare compare
186
+ compare_path = into_path / 'compare'
187
+ compare_path.mkdir(parents=True, exist_ok=True)
188
+ for language in extension.languages:
189
+ (compare_path / language).write_text(self._get_compare())
190
+
191
+ # Prepare run
192
+ run_path = into_path / 'run'
193
+ run_path.mkdir(parents=True, exist_ok=True)
194
+ for language in extension.languages:
195
+ run_orig_path = (
196
+ get_default_app_path() / 'packagers' / 'boca' / 'run' / language
197
+ )
198
+ if not run_orig_path.is_file():
199
+ console.console.print(
200
+ f'[error]Run script for language [item]{language}[/item] not found.[/error]'
201
+ )
202
+ raise typer.Exit(1)
203
+ shutil.copyfile(run_orig_path, run_path / language)
204
+
205
+ # Prepare compile.
206
+ compile_path = into_path / 'compile'
207
+ compile_path.mkdir(parents=True, exist_ok=True)
208
+ for language in extension.languages:
209
+ (compile_path / language).write_text(self._get_compile(language))
210
+
211
+ # Prepare tests
212
+ tests_path = into_path / 'tests'
213
+ tests_path.mkdir(parents=True, exist_ok=True)
214
+ for language in extension.languages:
215
+ (tests_path / language).write_text('exit 0\n')
216
+
217
+ # Problem statement
218
+ description_path = into_path / 'description'
219
+ description_path.mkdir(parents=True, exist_ok=True)
220
+ (description_path / 'problem.info').write_text(self._get_problem_info())
221
+ shutil.copyfile(
222
+ self._get_main_built_statement(built_statements).path,
223
+ (description_path / self._get_problem_name()).with_suffix('.pdf'),
224
+ )
225
+
226
+ # Prepare IO
227
+ inputs_path = into_path / 'input'
228
+ inputs_path.mkdir(parents=True, exist_ok=True)
229
+ outputs_path = into_path / 'output'
230
+ outputs_path.mkdir(parents=True, exist_ok=True)
231
+
232
+ testcases = self.get_flattened_built_testcases()
233
+ for i, testcase in enumerate(testcases):
234
+ shutil.copyfile(testcase.inputPath, inputs_path / f'{i+1:03d}')
235
+ if testcase.outputPath is not None:
236
+ shutil.copyfile(testcase.outputPath, outputs_path / f'{i+1:03d}')
237
+ else:
238
+ (outputs_path / f'{i+1:03d}').touch()
239
+
240
+ # Zip all.
241
+ shutil.make_archive(
242
+ str(build_path / self._get_problem_name()), 'zip', into_path
243
+ )
244
+
245
+ return (build_path / self._get_problem_name()).with_suffix('.zip')
@@ -0,0 +1,82 @@
1
+ import pathlib
2
+ import tempfile
3
+ from typing import Type
4
+
5
+ import typer
6
+
7
+ from rbx import annotations, console, utils
8
+ from rbx.box import environment, package
9
+ from rbx.box.contest import build_contest_statements, contest_package, contest_utils
10
+ from rbx.box.packaging.main import run_packager
11
+ from rbx.box.packaging.packager import (
12
+ BaseContestPackager,
13
+ BasePackager,
14
+ BuiltContestStatement,
15
+ BuiltProblemPackage,
16
+ )
17
+ from rbx.box.packaging.polygon.packager import PolygonContestPackager, PolygonPackager
18
+
19
+ app = typer.Typer(no_args_is_help=True, cls=annotations.AliasGroup)
20
+
21
+
22
+ def run_contest_packager(
23
+ contest_packager_cls: Type[BaseContestPackager],
24
+ packager_cls: Type[BasePackager],
25
+ verification: environment.VerificationParam,
26
+ ):
27
+ contest = contest_package.find_contest_package_or_die()
28
+
29
+ # Build problem-level packages.
30
+ built_packages = []
31
+ for problem in contest.problems:
32
+ console.console.print(
33
+ f'Processing problem [item]{problem.short_name}[/item]...'
34
+ )
35
+ with utils.new_cd(problem.get_path()):
36
+ contest_utils.clear_package_cache()
37
+ package_path = run_packager(packager_cls, verification=verification)
38
+ built_packages.append(
39
+ BuiltProblemPackage(
40
+ path=problem.get_path() / package_path,
41
+ package=package.find_problem_package_or_die(),
42
+ problem=problem,
43
+ )
44
+ )
45
+
46
+ # Build statements.
47
+ packager = contest_packager_cls()
48
+ statement_types = packager.statement_types()
49
+ built_statements = []
50
+
51
+ for statement_type in statement_types:
52
+ languages = packager.languages()
53
+ for language in languages:
54
+ statement = packager.get_statement_for_language(language)
55
+ statement_path = build_contest_statements.build_statement(
56
+ statement, contest, statement_type
57
+ )
58
+ built_statements.append(
59
+ BuiltContestStatement(statement, statement_path, statement_type)
60
+ )
61
+
62
+ console.console.print(f'Packaging contest for [item]{packager.name()}[/item]...')
63
+
64
+ # Build contest-level package.
65
+ with tempfile.TemporaryDirectory() as td:
66
+ packager.package(
67
+ built_packages, pathlib.Path('build'), pathlib.Path(td), built_statements
68
+ )
69
+
70
+ console.console.print(
71
+ f'[success]Contest packaged for [item]{packager.name()}[/item]![/success]'
72
+ )
73
+
74
+
75
+ @app.command('polygon', help='Build a contest package for Polygon.')
76
+ @contest_package.within_contest
77
+ def polygon(
78
+ verification: environment.VerificationParam,
79
+ ):
80
+ run_contest_packager(
81
+ PolygonContestPackager, PolygonPackager, verification=verification
82
+ )