rbx.cp 0.5.11__tar.gz → 0.5.13__tar.gz

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 (163) hide show
  1. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/PKG-INFO +1 -1
  2. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/pyproject.toml +1 -1
  3. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/builder.py +43 -13
  4. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/cd.py +1 -2
  5. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/checkers.py +25 -0
  6. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/compile.py +17 -23
  7. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/contest/contest_package.py +7 -1
  8. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/contest/main.py +26 -0
  9. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/contest/statements.py +8 -1
  10. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/environment.py +12 -6
  11. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/generators.py +8 -4
  12. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/main.py +92 -15
  13. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/package.py +7 -1
  14. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/presets/__init__.py +12 -0
  15. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/solutions.py +33 -9
  16. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/statements/build_statements.py +9 -1
  17. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/stresses.py +2 -1
  18. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/validators.py +41 -0
  19. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/grading/steps.py +60 -0
  20. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/LICENSE +0 -0
  21. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/README.md +0 -0
  22. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/__init__.py +0 -0
  23. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/annotations.py +0 -0
  24. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/autoenum.py +0 -0
  25. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/__init__.py +0 -0
  26. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/code.py +0 -0
  27. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/conftest.py +0 -0
  28. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/contest/__init__.py +0 -0
  29. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/contest/build_contest_statements.py +0 -0
  30. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/contest/contest_utils.py +0 -0
  31. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/contest/schema.py +0 -0
  32. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/creation.py +0 -0
  33. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/download.py +0 -0
  34. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/extensions.py +0 -0
  35. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/generators_test.py +0 -0
  36. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/packaging/boca/extension.py +0 -0
  37. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/packaging/boca/packager.py +0 -0
  38. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/packaging/contest_main.py +0 -0
  39. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/packaging/main.py +0 -0
  40. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/packaging/packager.py +0 -0
  41. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/packaging/polygon/packager.py +0 -0
  42. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/packaging/polygon/test.py +0 -0
  43. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/packaging/polygon/xml_schema.py +0 -0
  44. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/presets/fetch.py +0 -0
  45. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/presets/lock_schema.py +0 -0
  46. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/presets/schema.py +0 -0
  47. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/schema.py +0 -0
  48. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/solutions_test.py +0 -0
  49. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/statements/__init__.py +0 -0
  50. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/statements/builders.py +0 -0
  51. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/statements/joiners.py +0 -0
  52. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/statements/latex.py +0 -0
  53. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/statements/latex_jinja.py +0 -0
  54. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/statements/schema.py +0 -0
  55. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/stressing/__init__.py +0 -0
  56. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/stressing/finder_parser.py +0 -0
  57. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/stressing/generator_parser.py +0 -0
  58. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/testcases.py +0 -0
  59. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/ui/__init__.py +0 -0
  60. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/ui/captured_log.py +0 -0
  61. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/ui/css/app.tcss +0 -0
  62. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/ui/main.py +0 -0
  63. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/ui/run.py +0 -0
  64. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/box/validators_test.py +0 -0
  65. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/checker.py +0 -0
  66. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/clone.py +0 -0
  67. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/config.py +0 -0
  68. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/conftest.py +0 -0
  69. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/console.py +0 -0
  70. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/create.py +0 -0
  71. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/edit.py +0 -0
  72. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/grading/__init__.py +0 -0
  73. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/grading/caching.py +0 -0
  74. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/grading/conftest.py +0 -0
  75. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/grading/judge/__init__.py +0 -0
  76. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/grading/judge/cacher.py +0 -0
  77. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/grading/judge/digester.py +0 -0
  78. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/grading/judge/sandbox.py +0 -0
  79. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/grading/judge/sandboxes/__init__.py +0 -0
  80. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/grading/judge/sandboxes/isolate.py +0 -0
  81. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/grading/judge/sandboxes/stupid_sandbox.py +0 -0
  82. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/grading/judge/sandboxes/timeit.py +0 -0
  83. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/grading/judge/storage.py +0 -0
  84. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/grading/judge/test.py +0 -0
  85. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/grading/judge/testiso.py +0 -0
  86. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/grading/steps_with_caching.py +0 -0
  87. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/grading/steps_with_caching_run_test.py +0 -0
  88. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/grading_utils.py +0 -0
  89. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/hydration.py +0 -0
  90. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/main.py +0 -0
  91. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/metadata.py +0 -0
  92. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/providers/__init__.py +0 -0
  93. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/providers/codeforces.py +0 -0
  94. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/providers/provider.py +0 -0
  95. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/checkers/boilerplate.cpp +0 -0
  96. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/default_config.json +0 -0
  97. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/envs/default.rbx.yml +0 -0
  98. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/envs/isolate.rbx.yml +0 -0
  99. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/packagers/boca/checker.sh +0 -0
  100. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/packagers/boca/compare +0 -0
  101. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/packagers/boca/compile/c +0 -0
  102. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/packagers/boca/compile/cc +0 -0
  103. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/packagers/boca/compile/cpp +0 -0
  104. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/packagers/boca/compile/java +0 -0
  105. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/packagers/boca/compile/kt +0 -0
  106. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/packagers/boca/compile/pas +0 -0
  107. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/packagers/boca/compile/py2 +0 -0
  108. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/packagers/boca/compile/py3 +0 -0
  109. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/packagers/boca/run/c +0 -0
  110. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/packagers/boca/run/cc +0 -0
  111. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/packagers/boca/run/cpp +0 -0
  112. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/packagers/boca/run/java +0 -0
  113. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/packagers/boca/run/kt +0 -0
  114. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/packagers/boca/run/py2 +0 -0
  115. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/packagers/boca/run/py3 +0 -0
  116. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/presets/default/contest/contest.rbx.yml +0 -0
  117. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/presets/default/contest/statement/contest.rbx.tex +0 -0
  118. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/presets/default/contest/statement/olymp.sty +0 -0
  119. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/presets/default/contest/statement/template.rbx.tex +0 -0
  120. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/presets/default/preset.rbx.yml +0 -0
  121. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/presets/default/problem/.gitignore +0 -0
  122. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/presets/default/problem/gen.cpp +0 -0
  123. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/presets/default/problem/problem.rbx.yml +0 -0
  124. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/presets/default/problem/random.py +0 -0
  125. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/presets/default/problem/random.txt +0 -0
  126. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/presets/default/problem/sols/main.cpp +0 -0
  127. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/presets/default/problem/sols/slow.cpp +0 -0
  128. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/presets/default/problem/sols/wa.cpp +0 -0
  129. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/presets/default/problem/statement/olymp.sty +0 -0
  130. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/presets/default/problem/statement/projecao.png +0 -0
  131. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/presets/default/problem/statement/statement.rbx.tex +0 -0
  132. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/presets/default/problem/statement/template.rbx.tex +0 -0
  133. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/presets/default/problem/tests/samples/000.in +0 -0
  134. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/presets/default/problem/tests/samples/001.in +0 -0
  135. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/presets/default/problem/validator.cpp +0 -0
  136. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/presets/default/problem/wcmp.cpp +0 -0
  137. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/resources/templates/template.cpp +0 -0
  138. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/run.py +0 -0
  139. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/schema.py +0 -0
  140. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/submit.py +0 -0
  141. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/submitors/__init__.py +0 -0
  142. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/submitors/codeforces.py +0 -0
  143. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/submitors/submitor.py +0 -0
  144. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/test.py +0 -0
  145. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/testcase.py +0 -0
  146. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/testcase_rendering.py +0 -0
  147. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/testdata/box1/gen1.cpp +0 -0
  148. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/testdata/box1/gen2.cpp +0 -0
  149. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/testdata/box1/genScript.py +0 -0
  150. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/testdata/box1/hard-tle.sol.cpp +0 -0
  151. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/testdata/box1/ole.cpp +0 -0
  152. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/testdata/box1/problem.rbx.yml +0 -0
  153. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/testdata/box1/re.sol.cpp +0 -0
  154. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/testdata/box1/sol.cpp +0 -0
  155. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/testdata/box1/tests/1.in +0 -0
  156. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/testdata/box1/tle-and-incorrect.sol.cpp +0 -0
  157. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/testdata/box1/tle.sol.cpp +0 -0
  158. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/testdata/box1/validator.cpp +0 -0
  159. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/testdata/box1/wa.sol.cpp +0 -0
  160. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/testdata/caching/executable.py +0 -0
  161. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/testdata/compatible +0 -0
  162. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/testing_utils.py +0 -0
  163. {rbx_cp-0.5.11 → rbx_cp-0.5.13}/rbx/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: rbx.cp
3
- Version: 0.5.11
3
+ Version: 0.5.13
4
4
  Summary:
5
5
  Author: Roberto Sales
6
6
  Requires-Python: >=3.9,<4.0
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "rbx.cp"
3
- version = "0.5.11"
3
+ version = "0.5.13"
4
4
  description = ""
5
5
  packages = [
6
6
  {include = "rbx"}
@@ -9,14 +9,23 @@ from rbx.box.solutions import (
9
9
  print_run_report,
10
10
  run_solutions,
11
11
  )
12
- from rbx.box.validators import print_validation_report, validate_testcases
12
+ from rbx.box.validators import (
13
+ has_validation_errors,
14
+ print_validation_report,
15
+ validate_testcases,
16
+ )
13
17
 
14
18
 
15
19
  def build(
16
20
  verification: environment.VerificationParam,
17
21
  groups: Optional[Set[str]] = None,
18
- output: bool = True,
19
- ) -> None:
22
+ output: Optional[bool] = True,
23
+ ) -> bool:
24
+ no_main_solution_report = False
25
+ if output is None:
26
+ output = package.get_main_solution() is not None
27
+ no_main_solution_report = not output
28
+
20
29
  with utils.StatusProgress(
21
30
  'Building testcases...',
22
31
  'Built [item]{processed}[/item] testcases...',
@@ -24,6 +33,28 @@ def build(
24
33
  ) as s:
25
34
  generate_testcases(s, groups=groups)
26
35
 
36
+ if verification > 0:
37
+ validator = package.get_validator_or_nil()
38
+ if validator is None:
39
+ console.console.print(
40
+ '[warning]No validator found, skipping validation.[/warning]'
41
+ )
42
+
43
+ if validator is not None:
44
+ with utils.StatusProgress(
45
+ 'Validating testcases...',
46
+ 'Validated [item]{processed}[/item] testcases...',
47
+ keep=True,
48
+ ) as s:
49
+ infos = validate_testcases(s, groups=groups)
50
+ print_validation_report(infos)
51
+
52
+ if has_validation_errors(infos):
53
+ console.console.print(
54
+ '[error]Validation failed, check the report above.[/error]'
55
+ )
56
+ return False
57
+
27
58
  with utils.StatusProgress(
28
59
  'Building outputs for testcases...',
29
60
  'Built [item]{processed}[/item] outputs...',
@@ -32,23 +63,22 @@ def build(
32
63
  if output:
33
64
  generate_outputs_for_testcases(s, groups=groups)
34
65
 
35
- if verification > 0:
36
- with utils.StatusProgress(
37
- 'Validating testcases...',
38
- 'Validated [item]{processed}[/item] testcases...',
39
- keep=True,
40
- ) as s:
41
- infos = validate_testcases(s, groups=groups)
42
- print_validation_report(infos)
43
-
44
66
  console.console.print(
45
67
  '[success]Problem built.[/success] '
46
68
  '[warning]Check the output for verification errors![/warning]'
47
69
  )
48
70
 
71
+ if no_main_solution_report:
72
+ console.console.print(
73
+ '[warning]No main solution found, skipping generating samples for the statement.[/warning]'
74
+ )
75
+
76
+ return True
77
+
49
78
 
50
79
  def verify(verification: environment.VerificationParam) -> bool:
51
- build(verification=verification)
80
+ if not build(verification=verification):
81
+ return False
52
82
 
53
83
  if verification < VerificationLevel.FAST_SOLUTIONS.value:
54
84
  return True
@@ -3,9 +3,8 @@ import pathlib
3
3
  from typing import Optional
4
4
 
5
5
  import typer
6
- from rich import console
7
6
 
8
- from rbx import utils
7
+ from rbx import console, utils
9
8
 
10
9
 
11
10
  def find_package(root: pathlib.Path = pathlib.Path()) -> Optional[pathlib.Path]:
@@ -119,6 +119,31 @@ def check(
119
119
  )
120
120
  message = package.get_digest_as_string(error.value or '') or ''
121
121
 
122
+ if (
123
+ checker_run_log is not None
124
+ and checker_run_log.exitcode != 0
125
+ and (
126
+ checker_run_log.exitstatus != SandboxBase.EXIT_NONZERO_RETURN
127
+ or checker_run_log.exitcode not in [0, 1, 2, 3]
128
+ )
129
+ ):
130
+ console.console.print(
131
+ f'[error]Checker [item]{package.get_checker().path}[/item] failed unexpectedly.[/error]'
132
+ )
133
+ console.console.print(
134
+ f'[error]Summary:[/error] {checker_run_log.get_summary()}'
135
+ )
136
+ console.console.print(
137
+ f'[error]Testcase input:[/error] [item]{testcase.inputPath}[/item]'
138
+ )
139
+ console.console.print(
140
+ f'[error]Testcase output:[/error] [item]{testcase.outputPath}[/item]'
141
+ )
142
+ console.console.print(
143
+ f'[error]Program output:[/error] [item]{program_output}[/item]'
144
+ )
145
+ raise typer.Exit(1)
146
+
122
147
  if checker_run_log is None or checker_run_log.exitcode not in [0, 1, 2, 3]:
123
148
  return CheckerResult(outcome=Outcome.INTERNAL_ERROR)
124
149
 
@@ -22,35 +22,29 @@ def _compile(item: CodeItem):
22
22
  out_path.chmod(0o755)
23
23
 
24
24
  console.console.print(
25
- f'[success]Compiled file written at [item]{out_path}[/item].[/success]'
25
+ f'[success]Compiled file written at [item]{out_path}[/item][/success]'
26
26
  )
27
27
 
28
28
 
29
- @app.command('any, a', help='Compile an asset given its path.')
30
- @package.within_problem
31
29
  def any(path: str):
32
- _compile(CodeItem(path=pathlib.Path(path)))
33
-
34
-
35
- @app.command('solution, s', help='Compile a solution given its path.')
36
- @package.within_problem
37
- def solution(path: str):
38
- _compile(package.get_solution(path))
30
+ pkg = package.find_problem_package_or_die()
39
31
 
32
+ solution = package.get_solution_or_nil(path)
33
+ if solution is not None:
34
+ _compile(solution)
35
+ return
40
36
 
41
- @app.command('generator, gen, g', help='Compile a generator given its name.')
42
- @package.within_problem
43
- def generator(name: str):
44
- _compile(package.get_generator(name))
37
+ for generator in pkg.generators:
38
+ if generator.path == pathlib.Path(path) or generator.name == path:
39
+ _compile(generator)
40
+ return
45
41
 
42
+ if pkg.checker is not None and pkg.checker.path == pathlib.Path(path):
43
+ _compile(pkg.checker)
44
+ return
46
45
 
47
- @app.command('checker, c', help='Compile the checker.')
48
- @package.within_problem
49
- def checker():
50
- _compile(package.get_checker())
46
+ if pkg.validator is not None and pkg.validator.path == pathlib.Path(path):
47
+ _compile(pkg.validator)
48
+ return
51
49
 
52
-
53
- @app.command('validator, v', help='Compile the main validator.')
54
- @package.within_problem
55
- def validator():
56
- _compile(package.get_validator())
50
+ _compile(CodeItem(path=pathlib.Path(path)))
@@ -3,6 +3,7 @@ import pathlib
3
3
  from typing import List, Optional
4
4
 
5
5
  import typer
6
+ from pydantic import ValidationError
6
7
 
7
8
  from rbx import console, utils
8
9
  from rbx.box.contest.schema import Contest
@@ -30,7 +31,12 @@ def find_contest_package(root: pathlib.Path = pathlib.Path()) -> Optional[Contes
30
31
  contest_yaml_path = find_contest_yaml(root)
31
32
  if not contest_yaml_path:
32
33
  return None
33
- return utils.model_from_yaml(Contest, contest_yaml_path.read_text())
34
+ try:
35
+ return utils.model_from_yaml(Contest, contest_yaml_path.read_text())
36
+ except ValidationError as e:
37
+ console.console.print(e)
38
+ console.console.print('[error]Error parsing contest.rbx.yml.[/error]')
39
+ raise typer.Exit(1) from e
34
40
 
35
41
 
36
42
  def find_contest_package_or_die(root: pathlib.Path = pathlib.Path()) -> Contest:
@@ -160,6 +160,32 @@ def add(path: str, short_name: str, preset: Optional[str] = None):
160
160
  )
161
161
 
162
162
 
163
+ @app.command('remove, r', help='Remove problem from contest.')
164
+ @within_contest
165
+ def remove(path_or_short_name: str):
166
+ contest = find_contest_package_or_die()
167
+
168
+ kept_problems = []
169
+ removed_problems = []
170
+ for problem in contest.problems:
171
+ if (
172
+ problem.path == pathlib.Path(path_or_short_name)
173
+ or problem.short_name == path_or_short_name
174
+ ):
175
+ removed_problems.append(problem)
176
+ else:
177
+ kept_problems.append(problem)
178
+
179
+ contest.problems = kept_problems
180
+ save_contest(contest)
181
+
182
+ for problem in removed_problems:
183
+ shutil.rmtree(str(problem.path), ignore_errors=True)
184
+ console.console.print(
185
+ f'Problem [item]{problem.short_name}[/item] removed from contest at [item]{problem.path}[/item].'
186
+ )
187
+
188
+
163
189
  @app.command(
164
190
  'each',
165
191
  help='Run a command for each problem in the contest.',
@@ -51,7 +51,14 @@ def build(
51
51
  )
52
52
  with utils.new_cd(problem.get_path()):
53
53
  contest_utils.clear_package_cache()
54
- builder.build(verification=verification, groups=set(['samples']))
54
+
55
+ if not builder.build(
56
+ verification=verification, groups=set(['samples']), output=None
57
+ ):
58
+ console.console.print(
59
+ '[error]Failed to build statements with samples, aborting.[/error]'
60
+ )
61
+ raise typer.Exit(1)
55
62
 
56
63
  contest = find_contest_package_or_die()
57
64
  candidate_languages = languages
@@ -4,7 +4,7 @@ from enum import Enum
4
4
  from typing import Annotated, List, Optional, Type, TypeVar
5
5
 
6
6
  import typer
7
- from pydantic import BaseModel, ConfigDict
7
+ from pydantic import BaseModel, ConfigDict, ValidationError
8
8
 
9
9
  from rbx import config, console, utils
10
10
  from rbx.box.extensions import Extensions, LanguageExtensions
@@ -19,9 +19,8 @@ class VerificationLevel(Enum):
19
19
  NONE = 0
20
20
  VALIDATE = 1
21
21
  FAST_SOLUTIONS = 2
22
- ASAN = 3
23
- ALL_SOLUTIONS = 4
24
- FULL = 5
22
+ ALL_SOLUTIONS = 3
23
+ FULL = 4
25
24
 
26
25
 
27
26
  VerificationParam = Annotated[
@@ -31,7 +30,7 @@ VerificationParam = Annotated[
31
30
  '--verification',
32
31
  '-v',
33
32
  help='Verification level to use when building package.',
34
- default_factory=lambda: VerificationLevel.ALL_SOLUTIONS.value,
33
+ default_factory=lambda: VerificationLevel.FULL.value,
35
34
  ),
36
35
  ]
37
36
 
@@ -193,7 +192,14 @@ def get_environment(env: Optional[str] = None) -> Environment:
193
192
  f'Environment file [item]{env_path}[/item] not found.', style='error'
194
193
  )
195
194
  raise typer.Exit()
196
- return utils.model_from_yaml(Environment, env_path.read_text())
195
+ try:
196
+ return utils.model_from_yaml(Environment, env_path.read_text())
197
+ except ValidationError as e:
198
+ console.console.print(e)
199
+ console.console.print(
200
+ f'[error]Error parsing environment file [item]{env_path}[/item].[/error]'
201
+ )
202
+ raise typer.Exit(1) from e
197
203
 
198
204
 
199
205
  @functools.cache
@@ -82,6 +82,8 @@ def _run_generator(
82
82
  console.console.print(
83
83
  f'[error]Failed generating test {i} from group path {group_path}[/error]',
84
84
  )
85
+ if run_log is not None:
86
+ console.console.print(f'[error]Summary:[/error] {run_log.get_summary()}')
85
87
  if generation_stderr.value is not None:
86
88
  console.console.print('[error]Stderr:[/error]')
87
89
  console.console.print(
@@ -142,9 +144,7 @@ def generate_output_for_testcase(
142
144
  f'[error]Failed generating output for [item]{testcase.inputPath}[/item][/error]',
143
145
  )
144
146
  if run_log is not None:
145
- console.console.print(
146
- f'[error]Main solution exited with code [item]{-run_log.exitcode}[/item][/error]',
147
- )
147
+ console.console.print(f'[error]Summary:[/error] {run_log.get_summary()}')
148
148
  checker_result = checkers.check_with_no_output(run_log)
149
149
  console.console.print(
150
150
  f'[warning]Time: [item]{run_log.time:.2f}s[/item][/warning]',
@@ -238,7 +238,7 @@ def _run_generator_script(testcase: TestcaseSubgroup, cacher: FileCacher) -> str
238
238
  )
239
239
  if run_log is not None:
240
240
  console.console.print(
241
- f'[error]Script exited with code [item]{-run_log.exitcode}[/item][/error]',
241
+ f'[error]Summary:[/error] {run_log.get_summary()}'
242
242
  )
243
243
  if run_stderr.value is not None:
244
244
  console.console.print('[error]Stderr:[/error]')
@@ -341,6 +341,10 @@ def generate_standalone(
341
341
  console.console.print(
342
342
  f'[error]Failed generating test using generator call [info]{call.name} {expanded_args_str}[/info].[/error]',
343
343
  )
344
+ if generation_log is not None:
345
+ console.console.print(
346
+ f'[error]Summary:[/error] {generation_log.get_summary()}'
347
+ )
344
348
  if generation_stderr.value is not None:
345
349
  console.console.print('[error]Stderr:[/error]')
346
350
  console.console.print(
@@ -3,11 +3,12 @@ from gevent import monkey
3
3
 
4
4
  monkey.patch_all()
5
5
 
6
+ import tempfile
6
7
  import shlex
7
8
  import sys
8
9
  import typing
9
10
 
10
- from rbx.box.schema import CodeItem, ExpectedOutcome
11
+ from rbx.box.schema import CodeItem, ExpectedOutcome, TestcaseGroup
11
12
 
12
13
 
13
14
  import pathlib
@@ -31,6 +32,7 @@ from rbx.box import (
31
32
  compile,
32
33
  presets,
33
34
  stresses,
35
+ validators,
34
36
  )
35
37
  from rbx.box.contest import main as contest
36
38
  from rbx.box.environment import VerificationLevel, get_environment_path
@@ -69,9 +71,6 @@ app.add_typer(
69
71
  app.add_typer(
70
72
  contest.app, name='contest', cls=annotations.AliasGroup, help='Contest management.'
71
73
  )
72
- app.add_typer(
73
- compile.app, name='compile', cls=annotations.AliasGroup, help='Compile assets.'
74
- )
75
74
 
76
75
 
77
76
  @app.command('ui', hidden=True)
@@ -96,13 +95,6 @@ def build(verification: environment.VerificationParam):
96
95
  builder.build(verification=verification)
97
96
 
98
97
 
99
- @app.command('verify, v', help='Build and verify all the tests for the problem.')
100
- @package.within_problem
101
- def verify(verification: environment.VerificationParam):
102
- if not builder.verify(verification=verification):
103
- console.console.print('[error]Verification failed, check the report.[/error]')
104
-
105
-
106
98
  @app.command('run, r', help='Build and run solution(s).')
107
99
  @package.within_problem
108
100
  def run(
@@ -139,7 +131,14 @@ def run(
139
131
  )
140
132
  check = False
141
133
 
142
- builder.build(verification=verification, output=check)
134
+ if not builder.build(verification=verification, output=check):
135
+ return
136
+
137
+ if verification <= VerificationLevel.VALIDATE.value:
138
+ console.console.print(
139
+ '[warning]Verification level is set to [item]validate (-v1)[/item], so rbx only build tests and validated them.[/warning]'
140
+ )
141
+ return
143
142
 
144
143
  with utils.StatusProgress('Running solutions...') as s:
145
144
  tracked_solutions = None
@@ -206,6 +205,12 @@ def irun(
206
205
  console.console.print(
207
206
  '[warning]Outputs will be written to files. If you wish to print them to the terminal, use the "-p" parameter.'
208
207
  )
208
+ if verification < VerificationLevel.ALL_SOLUTIONS.value:
209
+ console.console.print(
210
+ '[warning]Verification level should be at least [item]all solutions (-v4)[/item] to run solutions interactively.'
211
+ )
212
+ return
213
+
209
214
  main_solution = package.get_main_solution()
210
215
  if check and main_solution is None:
211
216
  console.console.print(
@@ -248,7 +253,13 @@ def create(
248
253
  @app.command('stress', help='Run a stress test.')
249
254
  @package.within_problem
250
255
  def stress(
251
- name: str,
256
+ name: Annotated[
257
+ str,
258
+ typer.Argument(
259
+ help='Name of the stress test to run (specified in problem.rbx.yml), '
260
+ 'or the generator to run, in case -g is specified.'
261
+ ),
262
+ ],
252
263
  generator_args: Annotated[
253
264
  Optional[str],
254
265
  typer.Option(
@@ -325,9 +336,27 @@ def stress(
325
336
 
326
337
  testgroup = questionary.select(
327
338
  'Choose the testgroup to add the tests to.\nOnly test groups that have a .txt generatorScript are shown below: ',
328
- choices=list(groups_by_name) + ['(skip)'],
339
+ choices=list(groups_by_name) + ['(create new script)', '(skip)'],
329
340
  ).ask()
330
341
 
342
+ if testgroup == '(create new script)':
343
+ new_script_name = questionary.text(
344
+ 'Enter the name of the new .txt generatorScript file: '
345
+ ).ask()
346
+ new_script_path = pathlib.Path(new_script_name).with_suffix('.txt')
347
+ new_script_path.parent.mkdir(parents=True, exist_ok=True)
348
+ new_script_path.touch()
349
+
350
+ # Temporarily create a new testgroup with the new script.
351
+ testgroup = new_script_path.stem
352
+ groups_by_name[testgroup] = TestcaseGroup(
353
+ name=testgroup, generatorScript=CodeItem(path=new_script_path)
354
+ )
355
+ console.console.print(
356
+ f'[warning]A testgroup for [item]{new_script_path}[/item] will not be automatically added to the problem.rbx.yml file for you.\n'
357
+ 'Please add it manually. [/warning]'
358
+ )
359
+
331
360
  if testgroup not in groups_by_name:
332
361
  break
333
362
  try:
@@ -355,6 +384,47 @@ def stress(
355
384
  break
356
385
 
357
386
 
387
+ @app.command('compile', help='Compile an asset given its path.')
388
+ @package.within_problem
389
+ def compile_command(
390
+ path: Annotated[str, typer.Argument(help='Path to the asset to compile.')],
391
+ ):
392
+ compile.any(path)
393
+
394
+
395
+ @app.command('validate', help='Run the validator in a one-off fashion, interactively.')
396
+ @package.within_problem
397
+ def validate(
398
+ path: Annotated[
399
+ Optional[str],
400
+ typer.Option('--path', '-p', help='Path to the testcase to validate.'),
401
+ ] = None,
402
+ ):
403
+ validator_tuple = validators.compile_main_validator()
404
+ if validator_tuple is None:
405
+ console.console.print('[error]No validator found for this problem.[/error]')
406
+ raise typer.Exit(1)
407
+
408
+ validator, validator_digest = validator_tuple
409
+
410
+ input = console.multiline_prompt('Testcase input')
411
+
412
+ if path is None:
413
+ with tempfile.TemporaryDirectory() as tmpdir:
414
+ tmppath = pathlib.Path(tmpdir) / '000.in'
415
+ tmppath.write_text(input)
416
+
417
+ info = validators.validate_one_off(
418
+ pathlib.Path(tmppath), validator, validator_digest
419
+ )
420
+ else:
421
+ info = validators.validate_one_off(
422
+ pathlib.Path(path), validator, validator_digest
423
+ )
424
+
425
+ validators.print_validation_report([info])
426
+
427
+
358
428
  @app.command('environment, env', help='Set or show the current box environment.')
359
429
  def environment_command(
360
430
  env: Annotated[Optional[str], typer.Argument()] = None,
@@ -417,10 +487,17 @@ def activate():
417
487
  console.console.print(
418
488
  '[error]Preset is not installed. Install it manually, or specify a URI in [item].preset-lock.yml[/item].[/error]'
419
489
  )
420
- raise
490
+ raise typer.Exit(1)
421
491
  presets.install(preset_lock.uri)
422
492
 
423
493
  preset = presets.get_installed_preset(preset_lock.preset_name)
494
+
495
+ # Install the environment from the preset if it's not already installed.
496
+ presets.optionally_install_environment_from_preset(
497
+ preset, root=presets.get_preset_installation_path(preset_lock.name)
498
+ )
499
+
500
+ # Activate the environment.
424
501
  if preset.env is not None:
425
502
  environment_command(preset.name)
426
503
 
@@ -3,6 +3,7 @@ import pathlib
3
3
  from typing import Dict, List, Optional, Tuple
4
4
 
5
5
  import typer
6
+ from pydantic import ValidationError
6
7
 
7
8
  from rbx import config, console, utils
8
9
  from rbx.box import environment
@@ -74,7 +75,12 @@ def find_problem_package(root: pathlib.Path = pathlib.Path()) -> Optional[Packag
74
75
  problem_yaml_path = find_problem_yaml(root)
75
76
  if not problem_yaml_path:
76
77
  return None
77
- return utils.model_from_yaml(Package, problem_yaml_path.read_text())
78
+ try:
79
+ return utils.model_from_yaml(Package, problem_yaml_path.read_text())
80
+ except ValidationError as e:
81
+ console.console.print(e)
82
+ console.console.print('[error]Error parsing problem.rbx.yml.[/error]')
83
+ raise typer.Exit(1) from e
78
84
 
79
85
 
80
86
  def find_problem_package_or_die(root: pathlib.Path = pathlib.Path()) -> Package:
@@ -294,6 +294,18 @@ def get_installed_preset(name: str, root: pathlib.Path = pathlib.Path()) -> Pres
294
294
  return preset
295
295
 
296
296
 
297
+ def optionally_install_environment_from_preset(
298
+ preset: Preset, root: pathlib.Path = pathlib.Path()
299
+ ):
300
+ if preset.env is None:
301
+ return
302
+ env_path = get_environment_path(preset.name)
303
+ if env_path.is_file():
304
+ return
305
+ env_path.parent.mkdir(parents=True, exist_ok=True)
306
+ shutil.copyfile(str(root / preset.env), env_path)
307
+
308
+
297
309
  def _install(root: pathlib.Path = pathlib.Path(), force: bool = False):
298
310
  preset = get_preset_yaml(root)
299
311
 
@@ -378,6 +378,7 @@ def _run_interactive_solutions(
378
378
  shutil.rmtree(str(irun_dir), ignore_errors=True)
379
379
  irun_dir.mkdir(parents=True, exist_ok=True)
380
380
  inputs_dir = irun_dir / 'inputs'
381
+ inputs_dir.mkdir(parents=True, exist_ok=True)
381
382
  input_path = inputs_dir / '000.in'
382
383
  output_path = input_path.with_suffix('.out')
383
384
 
@@ -479,10 +480,14 @@ def get_testcase_markup_verdict(eval: Evaluation) -> str:
479
480
 
480
481
 
481
482
  def _get_evals_time_in_ms(evals: List[Evaluation]) -> int:
483
+ if not evals:
484
+ return 0
482
485
  return max(int((eval.log.time or 0.0) * 1000) for eval in evals)
483
486
 
484
487
 
485
488
  def _get_evals_memory_in_mb(evals: List[Evaluation]) -> int:
489
+ if not evals:
490
+ return 0
486
491
  return max(int(eval.log.memory or 0) // (1024 * 1024) for eval in evals)
487
492
 
488
493
 
@@ -504,6 +509,7 @@ def _print_solution_outcome(
504
509
  ) -> bool:
505
510
  pkg = package.find_problem_package_or_die()
506
511
 
512
+ has_plain_tle = False
507
513
  bad_verdicts = set()
508
514
  no_tle_bad_verdicts = set()
509
515
  for eval in evals:
@@ -514,6 +520,10 @@ def _print_solution_outcome(
514
520
  and eval.result.no_tle_outcome != Outcome.ACCEPTED
515
521
  ):
516
522
  no_tle_bad_verdicts.add(eval.result.no_tle_outcome)
523
+ has_plain_tle = has_plain_tle or (
524
+ eval.result.outcome == Outcome.TIME_LIMIT_EXCEEDED
525
+ and eval.result.no_tle_outcome is None
526
+ )
517
527
 
518
528
  unmatched_bad_verdicts = set(
519
529
  v for v in bad_verdicts if not solution.outcome.match(v)
@@ -542,16 +552,27 @@ def _print_solution_outcome(
542
552
  verification.value >= VerificationLevel.FULL.value
543
553
  # Solution expects a TLE.
544
554
  and expected_outcome_is_tle
545
- # A TLE (or similar) has happened.
546
- and matched_bad_verdicts
547
- # The solution has no other bad verdicts except for TLEs in double TL.
548
- and not ((bad_verdicts | no_tle_bad_verdicts) - {Outcome.TIME_LIMIT_EXCEEDED})
549
- # The solution passes in double TL.
555
+ # Solution does not have a plain TLE.
556
+ and not has_plain_tle
557
+ # A TLE has happened.
558
+ and Outcome.TIME_LIMIT_EXCEEDED in matched_bad_verdicts
559
+ # The solution runs in double TL.
550
560
  and evals_time < pkg.timelimit_for_language(solution.language) * 2
551
561
  ):
552
- console.print(
553
- '[yellow]WARNING[/yellow] The solution still passed in double TL.'
554
- )
562
+ other_verdicts = (bad_verdicts | no_tle_bad_verdicts) - {
563
+ Outcome.TIME_LIMIT_EXCEEDED
564
+ }
565
+ if not other_verdicts:
566
+ # The solution has no other bad verdicts except for TLEs in double TL.
567
+ console.print(
568
+ '[yellow]WARNING[/yellow] The solution still passed in double TL.'
569
+ )
570
+ elif not (bad_verdicts - {Outcome.TIME_LIMIT_EXCEEDED}):
571
+ # The solution has other bad soft TLE outcomes.
572
+ other_verdicts_names = ' '.join(v.name for v in other_verdicts)
573
+ console.print(
574
+ f'[yellow]WARNING[/yellow] The solution could still run under double TL, but failed with [item]{other_verdicts_names}[/item].'
575
+ )
555
576
  console.print(f'Time: {get_evals_formatted_time(evals)}')
556
577
  console.print(f'Memory: {get_evals_formatted_memory(evals)}')
557
578
  return len(unmatched_bad_verdicts) == 0
@@ -787,6 +808,9 @@ def print_run_report(
787
808
  print_last_solution()
788
809
 
789
810
  items.seek(0)
790
- _print_timing(console, result.skeleton, list(structured_evaluations)[-1])
811
+ structured_evaluations_list = list(structured_evaluations)
812
+
813
+ if structured_evaluations_list:
814
+ _print_timing(console, result.skeleton, structured_evaluations_list[-1])
791
815
 
792
816
  return ok