rbx.cp 0.12.0__py3-none-any.whl → 0.13.3__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 CHANGED
@@ -186,6 +186,15 @@ def on(ctx: typer.Context, problems: str) -> None:
186
186
  contest.on(ctx, problems)
187
187
 
188
188
 
189
+ @app.command(
190
+ 'each',
191
+ help='Run a command for each problem in the contest.',
192
+ context_settings={'allow_extra_args': True, 'ignore_unknown_options': True},
193
+ )
194
+ def each(ctx: typer.Context) -> None:
195
+ contest.each(ctx)
196
+
197
+
189
198
  @app.command('diff', hidden=True)
190
199
  def diff(path1: pathlib.Path, path2: pathlib.Path):
191
200
  from rbx.box.ui import main as ui_pkg
rbx/box/generators.py CHANGED
@@ -97,7 +97,10 @@ def _copy_testcase_over(
97
97
 
98
98
 
99
99
  def _copy_testcase_output_over(
100
- src_output_path: pathlib.Path, dest_output_path: pathlib.Path, suffix: str
100
+ src_output_path: pathlib.Path,
101
+ dest_output_path: pathlib.Path,
102
+ suffix: str,
103
+ dry_run: bool = False,
101
104
  ) -> bool:
102
105
  dest_output_path.parent.mkdir(parents=True, exist_ok=True)
103
106
 
@@ -105,41 +108,64 @@ def _copy_testcase_output_over(
105
108
  if not src_path.is_file():
106
109
  return False
107
110
 
108
- _check_crlf(src_path)
111
+ if dry_run:
112
+ return True
109
113
 
114
+ _check_crlf(src_path)
110
115
  shutil.copy(str(src_path), str(dest_output_path.with_suffix(suffix)))
111
116
  return True
112
117
 
113
118
 
114
119
  def _copy_testcase_outputs_over(
115
- testcase: Testcase, dest: Testcase, pipes: bool = False
120
+ testcase: Testcase, dest: Testcase, pipes: bool = False, dry_run: bool = False
116
121
  ):
117
122
  assert dest.outputPath is not None
118
- dest.outputPath.parent.mkdir(parents=True, exist_ok=True)
123
+ if not dry_run:
124
+ dest.outputPath.parent.mkdir(parents=True, exist_ok=True)
119
125
 
120
126
  has_copied = False
121
127
 
122
128
  if testcase.outputPath is not None and testcase.outputPath.is_file():
123
- _check_crlf(testcase.outputPath)
124
- shutil.copy(str(testcase.outputPath), str(dest.outputPath))
129
+ if not dry_run:
130
+ _check_crlf(testcase.outputPath)
131
+ shutil.copy(str(testcase.outputPath), str(dest.outputPath))
125
132
  has_copied = True
126
133
 
127
134
  if not pipes:
128
135
  return has_copied
129
136
 
130
137
  reference_path = testcase.outputPath or testcase.inputPath
131
- if _copy_testcase_output_over(reference_path, dest.outputPath, '.pin'):
138
+ if _copy_testcase_output_over(
139
+ reference_path, dest.outputPath, '.pin', dry_run=dry_run
140
+ ):
132
141
  has_copied = True
133
142
 
134
- if _copy_testcase_output_over(reference_path, dest.outputPath, '.pout'):
143
+ if _copy_testcase_output_over(
144
+ reference_path, dest.outputPath, '.pout', dry_run=dry_run
145
+ ):
135
146
  has_copied = True
136
147
 
137
- if _copy_testcase_output_over(reference_path, dest.outputPath, '.pio'):
148
+ if _copy_testcase_output_over(
149
+ reference_path, dest.outputPath, '.pio', dry_run=dry_run
150
+ ):
138
151
  has_copied = True
139
152
 
140
153
  return has_copied
141
154
 
142
155
 
156
+ def _needs_output(generation_entries: List[GenerationTestcaseEntry]) -> bool:
157
+ for entry in generation_entries:
158
+ tc = entry.metadata.copied_to
159
+ if not tc.inputPath.is_file():
160
+ continue
161
+ if entry.metadata.copied_from is not None and _copy_testcase_outputs_over(
162
+ entry.metadata.copied_from, tc, dry_run=True
163
+ ):
164
+ continue
165
+ return True
166
+ return False
167
+
168
+
143
169
  def get_all_built_testcases() -> Dict[str, List[Testcase]]:
144
170
  pkg = package.find_problem_package_or_die()
145
171
  res = {group.name: find_built_testcases(group) for group in pkg.testcases}
@@ -395,17 +421,20 @@ async def generate_outputs_for_testcases(
395
421
  if progress is not None:
396
422
  progress.step()
397
423
 
424
+ generation_entries = await extract_generation_testcases(entries)
425
+ needs_output = _needs_output(generation_entries)
426
+
398
427
  main_solution = package.get_main_solution()
399
428
  solution_digest: Optional[str] = None
400
429
 
401
430
  pkg = package.find_problem_package_or_die()
402
431
 
403
- if pkg.type == TaskType.COMMUNICATION:
432
+ if pkg.type == TaskType.COMMUNICATION and needs_output:
404
433
  interactor_digest = checkers.compile_interactor(progress)
405
434
  else:
406
435
  interactor_digest = None
407
436
 
408
- if main_solution is not None:
437
+ if main_solution is not None and needs_output:
409
438
  if progress:
410
439
  progress.update('Compiling main solution...')
411
440
  try:
@@ -418,8 +447,6 @@ async def generate_outputs_for_testcases(
418
447
  shutil.rmtree(str(gen_runs_dir), ignore_errors=True)
419
448
  gen_runs_dir.mkdir(parents=True, exist_ok=True)
420
449
 
421
- generation_entries = await extract_generation_testcases(entries)
422
-
423
450
  for entry in generation_entries:
424
451
  tc = entry.metadata.copied_to
425
452
  if not tc.inputPath.is_file():
@@ -434,6 +461,7 @@ async def generate_outputs_for_testcases(
434
461
  step()
435
462
  continue
436
463
 
464
+ assert needs_output
437
465
  if (
438
466
  main_solution is None or solution_digest is None
439
467
  ) and not tc.outputPath.is_file():
rbx/box/header.py CHANGED
@@ -16,6 +16,11 @@ def get_header() -> pathlib.Path:
16
16
 
17
17
 
18
18
  def generate_header():
19
+ override_header = pathlib.Path('rbx.override.h')
20
+ if override_header.is_file():
21
+ pathlib.Path('rbx.h').write_bytes(override_header.read_bytes())
22
+ return
23
+
19
24
  with importlib.resources.as_file(
20
25
  importlib.resources.files('rbx') / 'resources' / 'templates' / 'rbx.h'
21
26
  ) as file:
rbx/box/lang.py CHANGED
@@ -1,27 +1,40 @@
1
1
  import functools
2
- from typing import List
2
+ from typing import Dict, List
3
3
 
4
4
  import iso639
5
5
 
6
- from rbx import console
6
+
7
+ @functools.cache
8
+ def _get_lowercase_name_mapping() -> Dict[str, iso639.Lang]:
9
+ res = {}
10
+ for lang in iso639.iter_langs():
11
+ res[lang.name.lower()] = lang
12
+ return res
13
+
14
+
15
+ def _get_lang_name(lang: str) -> str:
16
+ mapping = _get_lowercase_name_mapping()
17
+ if lang.lower() in mapping:
18
+ return mapping[lang.lower()].name
19
+ return lang
20
+
21
+
22
+ def code_to_lang(lang: str) -> str:
23
+ return iso639.Lang(lang).name.lower()
7
24
 
8
25
 
9
26
  def code_to_langs(langs: List[str]) -> List[str]:
10
- return [iso639.Language.from_part1(lang).name.lower() for lang in langs]
27
+ return [code_to_lang(lang) for lang in langs]
11
28
 
12
29
 
13
30
  @functools.cache
14
31
  def is_valid_lang_code(lang: str) -> bool:
15
- try:
16
- code_to_langs([lang])
17
- except iso639.LanguageNotFoundError:
18
- console.console.print(
19
- f'[warning]Language [item]{lang}[/item] is being skipped because it is not a iso639 language.[/warning]'
20
- )
21
- return False
32
+ return iso639.is_language(lang)
33
+
22
34
 
23
- return True
35
+ def lang_to_code(lang: str) -> str:
36
+ return iso639.Lang(_get_lang_name(lang)).pt1
24
37
 
25
38
 
26
39
  def langs_to_code(langs: List[str]) -> List[str]:
27
- return [iso639.Language.from_name(lang).part1 for lang in langs]
40
+ return [lang_to_code(lang) for lang in langs]
@@ -1,6 +1,6 @@
1
1
  import pathlib
2
2
  import tempfile
3
- from typing import Type
3
+ from typing import Optional, Type
4
4
 
5
5
  import syncer
6
6
  import typer
@@ -8,12 +8,13 @@ import typer
8
8
  from rbx import annotations, console
9
9
  from rbx.box import cd, environment, package
10
10
  from rbx.box.contest import build_contest_statements, contest_package
11
- from rbx.box.packaging.main import run_packager
12
11
  from rbx.box.packaging.packager import (
13
12
  BaseContestPackager,
14
13
  BasePackager,
15
14
  BuiltContestStatement,
16
15
  BuiltProblemPackage,
16
+ ContestZipper,
17
+ run_packager,
17
18
  )
18
19
 
19
20
  app = typer.Typer(no_args_is_help=True, cls=annotations.AliasGroup)
@@ -23,6 +24,7 @@ async def run_contest_packager(
23
24
  contest_packager_cls: Type[BaseContestPackager],
24
25
  packager_cls: Type[BasePackager],
25
26
  verification: environment.VerificationParam,
27
+ **kwargs,
26
28
  ):
27
29
  contest = contest_package.find_contest_package_or_die()
28
30
 
@@ -34,7 +36,9 @@ async def run_contest_packager(
34
36
  )
35
37
  with cd.new_package_cd(problem.get_path()):
36
38
  package.clear_package_cache()
37
- package_path = await run_packager(packager_cls, verification=verification)
39
+ package_path = await run_packager(
40
+ packager_cls, verification=verification, **kwargs
41
+ )
38
42
  built_packages.append(
39
43
  BuiltProblemPackage(
40
44
  path=problem.get_path() / package_path,
@@ -44,7 +48,7 @@ async def run_contest_packager(
44
48
  )
45
49
 
46
50
  # Build statements.
47
- packager = contest_packager_cls()
51
+ packager = contest_packager_cls(**kwargs)
48
52
  statement_types = packager.statement_types()
49
53
  built_statements = []
50
54
 
@@ -63,12 +67,12 @@ async def run_contest_packager(
63
67
 
64
68
  # Build contest-level package.
65
69
  with tempfile.TemporaryDirectory() as td:
66
- packager.package(
70
+ result_path = packager.package(
67
71
  built_packages, pathlib.Path('build'), pathlib.Path(td), built_statements
68
72
  )
69
73
 
70
74
  console.console.print(
71
- f'[success]Contest packaged for [item]{packager.name()}[/item]![/success]'
75
+ f'[success]Created contest package for [item]{packager.name()}[/item] at [item]{result_path}[/item]![/success]'
72
76
  )
73
77
 
74
78
 
@@ -77,6 +81,12 @@ async def run_contest_packager(
77
81
  @syncer.sync
78
82
  async def polygon(
79
83
  verification: environment.VerificationParam,
84
+ language: Optional[str] = typer.Option(
85
+ None,
86
+ '--language',
87
+ '-l',
88
+ help='If set, will use the given language as the main language.',
89
+ ),
80
90
  ):
81
91
  from rbx.box.packaging.polygon.packager import (
82
92
  PolygonContestPackager,
@@ -84,7 +94,30 @@ async def polygon(
84
94
  )
85
95
 
86
96
  await run_contest_packager(
87
- PolygonContestPackager, PolygonPackager, verification=verification
97
+ PolygonContestPackager,
98
+ PolygonPackager,
99
+ verification=verification,
100
+ main_language=language,
101
+ )
102
+
103
+
104
+ @app.command('boca', help='Build a contest package for BOCA.')
105
+ @contest_package.within_contest
106
+ @syncer.sync
107
+ async def boca(
108
+ verification: environment.VerificationParam,
109
+ ):
110
+ from rbx.box.packaging.boca.packager import BocaPackager
111
+
112
+ class BocaContestPackager(ContestZipper):
113
+ def __init__(self, **kwargs):
114
+ super().__init__('boca-contest', zip_inner=True, **kwargs)
115
+
116
+ def name(self) -> str:
117
+ return 'boca'
118
+
119
+ await run_contest_packager(
120
+ BocaContestPackager, BocaPackager, verification=verification
88
121
  )
89
122
 
90
123
 
@@ -0,0 +1,37 @@
1
+ import pathlib
2
+ from abc import ABC, abstractmethod
3
+ from typing import Type
4
+
5
+ from rbx import console
6
+
7
+
8
+ class BaseImporter(ABC):
9
+ @classmethod
10
+ @abstractmethod
11
+ def name(cls) -> str:
12
+ pass
13
+
14
+ @abstractmethod
15
+ async def import_package(self, pkg_path: pathlib.Path, into_path: pathlib.Path):
16
+ pass
17
+
18
+
19
+ class BaseContestImporter(ABC):
20
+ @classmethod
21
+ @abstractmethod
22
+ def name(cls) -> str:
23
+ pass
24
+
25
+ @abstractmethod
26
+ async def import_package(self, pkg_path: pathlib.Path, into_path: pathlib.Path):
27
+ pass
28
+
29
+
30
+ async def run_importer(
31
+ importer_cls: Type[BaseImporter], pkg_path: pathlib.Path, into_path: pathlib.Path
32
+ ):
33
+ importer = importer_cls()
34
+ console.console.print(
35
+ f'Importing package from [item]{pkg_path}[/item] to [item]{into_path}[/item] with [item]{importer.name()}[/item]...'
36
+ )
37
+ await importer.import_package(pkg_path, into_path)
rbx/box/packaging/main.py CHANGED
@@ -1,72 +1,16 @@
1
- import pathlib
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, console
9
- from rbx.box import environment, header, package
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.package import get_build_path
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
- preserve_language: bool = typer.Option(
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
- '--preserve-language',
84
- help='If set, will preserve the original language of the statement.',
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(PolygonPackager, verification=verification)
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
- preserve_language=preserve_language,
48
+ main_language=language,
49
+ upload_as_english=upload_as_english,
97
50
  )
98
51
 
99
52
 
@@ -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
- from rbx.box import naming, package
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