rbx.cp 0.5.35__tar.gz → 0.5.37__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 (174) hide show
  1. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/PKG-INFO +1 -1
  2. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/pyproject.toml +1 -1
  3. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/builder.py +5 -2
  4. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/contest/main.py +2 -1
  5. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/creation.py +7 -12
  6. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/generators.py +17 -318
  7. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/generators_test.py +1 -1
  8. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/main.py +10 -0
  9. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/package.py +2 -2
  10. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/schema.py +8 -1
  11. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/solutions.py +1 -1
  12. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/solutions_test.py +1 -1
  13. rbx_cp-0.5.37/rbx/box/testcase_extractors.py +348 -0
  14. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/testcase_utils.py +17 -7
  15. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/testcases/main.py +8 -6
  16. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/validators.py +61 -33
  17. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/config.py +16 -2
  18. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/LICENSE +0 -0
  19. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/README.md +0 -0
  20. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/__init__.py +0 -0
  21. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/annotations.py +0 -0
  22. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/autoenum.py +0 -0
  23. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/__init__.py +0 -0
  24. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/cd.py +0 -0
  25. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/checkers.py +0 -0
  26. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/code.py +0 -0
  27. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/compile.py +0 -0
  28. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/conftest.py +0 -0
  29. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/contest/__init__.py +0 -0
  30. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/contest/build_contest_statements.py +0 -0
  31. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/contest/contest_package.py +0 -0
  32. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/contest/contest_utils.py +0 -0
  33. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/contest/schema.py +0 -0
  34. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/contest/statements.py +0 -0
  35. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/deferred.py +0 -0
  36. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/download.py +0 -0
  37. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/environment.py +0 -0
  38. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/extensions.py +0 -0
  39. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/formatting.py +0 -0
  40. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/packaging/boca/extension.py +0 -0
  41. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/packaging/boca/packager.py +0 -0
  42. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/packaging/contest_main.py +0 -0
  43. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/packaging/main.py +0 -0
  44. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/packaging/packager.py +0 -0
  45. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/packaging/polygon/packager.py +0 -0
  46. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/packaging/polygon/test.py +0 -0
  47. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/packaging/polygon/xml_schema.py +0 -0
  48. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/presets/__init__.py +0 -0
  49. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/presets/fetch.py +0 -0
  50. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/presets/lock_schema.py +0 -0
  51. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/presets/schema.py +0 -0
  52. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/retries.py +0 -0
  53. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/sanitizers/warning_stack.py +0 -0
  54. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/setter_config.py +0 -0
  55. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/state.py +0 -0
  56. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/statements/__init__.py +0 -0
  57. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/statements/build_statements.py +0 -0
  58. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/statements/builders.py +0 -0
  59. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/statements/joiners.py +0 -0
  60. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/statements/latex.py +0 -0
  61. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/statements/latex_jinja.py +0 -0
  62. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/statements/schema.py +0 -0
  63. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/stresses.py +0 -0
  64. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/stressing/__init__.py +0 -0
  65. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/stressing/finder_parser.py +0 -0
  66. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/stressing/generator_parser.py +0 -0
  67. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/testcases/__init__.py +0 -0
  68. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/ui/__init__.py +0 -0
  69. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/ui/captured_log.py +0 -0
  70. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/ui/css/app.tcss +0 -0
  71. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/ui/main.py +0 -0
  72. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/ui/run.py +0 -0
  73. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/box/validators_test.py +0 -0
  74. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/checker.py +0 -0
  75. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/clone.py +0 -0
  76. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/conftest.py +0 -0
  77. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/console.py +0 -0
  78. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/create.py +0 -0
  79. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/edit.py +0 -0
  80. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/grading/__init__.py +0 -0
  81. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/grading/caching.py +0 -0
  82. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/grading/conftest.py +0 -0
  83. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/grading/judge/__init__.py +0 -0
  84. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/grading/judge/cacher.py +0 -0
  85. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/grading/judge/digester.py +0 -0
  86. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/grading/judge/sandbox.py +0 -0
  87. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/grading/judge/sandboxes/__init__.py +0 -0
  88. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/grading/judge/sandboxes/isolate.py +0 -0
  89. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/grading/judge/sandboxes/stupid_sandbox.py +0 -0
  90. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/grading/judge/sandboxes/timeit.py +0 -0
  91. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/grading/judge/storage.py +0 -0
  92. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/grading/judge/test.py +0 -0
  93. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/grading/judge/testiso.py +0 -0
  94. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/grading/steps.py +0 -0
  95. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/grading/steps_with_caching.py +0 -0
  96. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/grading/steps_with_caching_run_test.py +0 -0
  97. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/grading_utils.py +0 -0
  98. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/hydration.py +0 -0
  99. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/main.py +0 -0
  100. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/metadata.py +0 -0
  101. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/providers/__init__.py +0 -0
  102. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/providers/codeforces.py +0 -0
  103. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/providers/provider.py +0 -0
  104. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/checkers/boilerplate.cpp +0 -0
  105. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/default_config.json +0 -0
  106. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/default_setter_config.mac.yml +0 -0
  107. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/default_setter_config.yml +0 -0
  108. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/envs/default.rbx.yml +0 -0
  109. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/envs/isolate.rbx.yml +0 -0
  110. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/packagers/boca/checker.sh +0 -0
  111. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/packagers/boca/compare +0 -0
  112. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/packagers/boca/compile/c +0 -0
  113. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/packagers/boca/compile/cc +0 -0
  114. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/packagers/boca/compile/cpp +0 -0
  115. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/packagers/boca/compile/java +0 -0
  116. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/packagers/boca/compile/kt +0 -0
  117. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/packagers/boca/compile/pas +0 -0
  118. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/packagers/boca/compile/py2 +0 -0
  119. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/packagers/boca/compile/py3 +0 -0
  120. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/packagers/boca/run/c +0 -0
  121. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/packagers/boca/run/cc +0 -0
  122. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/packagers/boca/run/cpp +0 -0
  123. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/packagers/boca/run/java +0 -0
  124. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/packagers/boca/run/kt +0 -0
  125. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/packagers/boca/run/py2 +0 -0
  126. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/packagers/boca/run/py3 +0 -0
  127. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/presets/default/contest/contest.rbx.yml +0 -0
  128. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/presets/default/contest/statement/contest.rbx.tex +0 -0
  129. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/presets/default/contest/statement/olymp.sty +0 -0
  130. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/presets/default/contest/statement/template.rbx.tex +0 -0
  131. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/presets/default/preset.rbx.yml +0 -0
  132. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/presets/default/problem/.gitignore +0 -0
  133. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/presets/default/problem/gen.cpp +0 -0
  134. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/presets/default/problem/problem.rbx.yml +0 -0
  135. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/presets/default/problem/random.py +0 -0
  136. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/presets/default/problem/random.txt +0 -0
  137. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/presets/default/problem/sols/main.cpp +0 -0
  138. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/presets/default/problem/sols/slow.cpp +0 -0
  139. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/presets/default/problem/sols/wa.cpp +0 -0
  140. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/presets/default/problem/statement/olymp.sty +0 -0
  141. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/presets/default/problem/statement/projecao.png +0 -0
  142. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/presets/default/problem/statement/statement.rbx.tex +0 -0
  143. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/presets/default/problem/statement/template.rbx.tex +0 -0
  144. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/presets/default/problem/tests/samples/000.in +0 -0
  145. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/presets/default/problem/tests/samples/001.in +0 -0
  146. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/presets/default/problem/validator.cpp +0 -0
  147. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/presets/default/problem/wcmp.cpp +0 -0
  148. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/resources/templates/template.cpp +0 -0
  149. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/run.py +0 -0
  150. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/schema.py +0 -0
  151. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/submit.py +0 -0
  152. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/submitors/__init__.py +0 -0
  153. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/submitors/codeforces.py +0 -0
  154. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/submitors/submitor.py +0 -0
  155. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/test.py +0 -0
  156. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/testcase.py +0 -0
  157. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/testcase_rendering.py +0 -0
  158. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/testdata/box1/gen1.cpp +0 -0
  159. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/testdata/box1/gen2.cpp +0 -0
  160. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/testdata/box1/genScript.py +0 -0
  161. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/testdata/box1/hard-tle.sol.cpp +0 -0
  162. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/testdata/box1/ole.cpp +0 -0
  163. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/testdata/box1/problem.rbx.yml +0 -0
  164. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/testdata/box1/re.sol.cpp +0 -0
  165. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/testdata/box1/sol.cpp +0 -0
  166. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/testdata/box1/tests/1.in +0 -0
  167. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/testdata/box1/tle-and-incorrect.sol.cpp +0 -0
  168. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/testdata/box1/tle.sol.cpp +0 -0
  169. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/testdata/box1/validator.cpp +0 -0
  170. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/testdata/box1/wa.sol.cpp +0 -0
  171. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/testdata/caching/executable.py +0 -0
  172. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/testdata/compatible +0 -0
  173. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/testing_utils.py +0 -0
  174. {rbx_cp-0.5.35 → rbx_cp-0.5.37}/rbx/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: rbx.cp
3
- Version: 0.5.35
3
+ Version: 0.5.37
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.35"
3
+ version = "0.5.37"
4
4
  description = ""
5
5
  packages = [
6
6
  {include = "rbx"}
@@ -4,7 +4,6 @@ from rbx import console, utils
4
4
  from rbx.box import environment, package
5
5
  from rbx.box.environment import VerificationLevel
6
6
  from rbx.box.generators import (
7
- extract_generation_testcases_from_groups,
8
7
  generate_outputs_for_testcases,
9
8
  generate_testcases,
10
9
  )
@@ -13,6 +12,7 @@ from rbx.box.solutions import (
13
12
  print_run_report,
14
13
  run_solutions,
15
14
  )
15
+ from rbx.box.testcase_extractors import extract_generation_testcases_from_groups
16
16
  from rbx.box.validators import (
17
17
  has_validation_errors,
18
18
  print_validation_report,
@@ -50,7 +50,10 @@ def build(
50
50
  'Validated [item]{processed}[/item] testcases...',
51
51
  keep=True,
52
52
  ) as s:
53
- infos = validate_testcases(s, groups=groups)
53
+ infos = validate_testcases(
54
+ s,
55
+ groups=groups,
56
+ )
54
57
  print_validation_report(infos)
55
58
 
56
59
  if has_validation_errors(infos):
@@ -128,7 +128,8 @@ def edit():
128
128
  @app.command('add, a', help='Add new problem to contest.')
129
129
  @within_contest
130
130
  def add(path: str, short_name: str, preset: Optional[str] = None):
131
- name = pathlib.Path(path).stem
131
+ problem_path = pathlib.Path(path)
132
+ name = problem_path.stem
132
133
  utils.validate_field(ContestProblem, 'short_name', short_name)
133
134
  utils.validate_field(Package, 'name', name)
134
135
 
@@ -4,9 +4,8 @@ from typing import Annotated, Optional
4
4
 
5
5
  import typer
6
6
 
7
- from rbx import console
8
- from rbx.box import presets
9
- from rbx.box.contest.contest_package import find_contest_yaml
7
+ from rbx import console, utils
8
+ from rbx.box import package, presets
10
9
  from rbx.box.presets.fetch import get_preset_fetch_info
11
10
 
12
11
 
@@ -27,15 +26,6 @@ def create(
27
26
  ] = None,
28
27
  path: Optional[pathlib.Path] = None,
29
28
  ):
30
- if find_contest_yaml() is not None:
31
- console.console.print(
32
- '[error]Cannot [item]rbx create[/item] a problem inside a contest.[/error]'
33
- )
34
- console.console.print(
35
- '[error]Instead, use [item]rbx contest add[/item] to add a problem to a contest.[/error]'
36
- )
37
- raise typer.Exit(1)
38
-
39
29
  preset = preset or 'default'
40
30
  console.console.print(f'Creating new problem [item]{name}[/item]...')
41
31
 
@@ -80,4 +70,9 @@ def create(
80
70
  for lock in dest_path.rglob('.preset-lock.yml'):
81
71
  lock.unlink(missing_ok=True)
82
72
 
73
+ # Change problem name.
74
+ ru, problem = package.get_ruyaml(dest_path)
75
+ problem['name'] = name
76
+ utils.save_ruyaml(dest_path / 'problem.rbx.yml', ru, problem)
77
+
83
78
  presets.generate_lock(preset, root=dest_path)
@@ -1,12 +1,8 @@
1
- import abc
2
1
  import pathlib
3
- import shlex
4
2
  import shutil
5
- from pathlib import PosixPath
6
- from typing import Dict, Iterable, List, Optional, Set, Tuple
3
+ from typing import Dict, List, Optional, Set
7
4
 
8
5
  import typer
9
- from pydantic import BaseModel
10
6
 
11
7
  from rbx import console
12
8
  from rbx.box import checkers, package, testcase_utils, validators
@@ -19,10 +15,20 @@ from rbx.box.schema import (
19
15
  CodeItem,
20
16
  GeneratorCall,
21
17
  Testcase,
22
- TestcaseSubgroup,
23
18
  )
24
19
  from rbx.box.stressing import generator_parser
25
- from rbx.box.testcase_utils import TestcaseEntry, TestcasePattern, find_built_testcases
20
+ from rbx.box.testcase_extractors import (
21
+ GenerationMetadata,
22
+ GenerationTestcaseEntry,
23
+ TestcaseGroupVisitor,
24
+ extract_generation_testcases,
25
+ run_testcase_visitor,
26
+ )
27
+ from rbx.box.testcase_utils import (
28
+ TestcaseEntry,
29
+ fill_output_for_defined_testcase,
30
+ find_built_testcases,
31
+ )
26
32
  from rbx.grading.steps import (
27
33
  DigestHolder,
28
34
  DigestOrDest,
@@ -35,33 +41,11 @@ def _compile_generator(generator: CodeItem) -> str:
35
41
  return compile_item(generator, sanitized=SanitizationLevel.PREFER)
36
42
 
37
43
 
38
- def _get_group_input(
39
- group_path: pathlib.Path, subgroup_prefix: str, i: int
40
- ) -> pathlib.Path:
41
- return group_path / f'{subgroup_prefix}{i:03d}.in'
42
-
43
-
44
- def _get_group_output(
45
- group_path: pathlib.Path, subgroup_prefix: str, i: int
46
- ) -> pathlib.Path:
47
- return group_path / f'{subgroup_prefix}{i:03d}.out'
48
-
49
-
50
- def _fill_output_for_defined_testcase(testcase: Testcase) -> Testcase:
51
- res = testcase.model_copy()
52
- if res.outputPath is not None:
53
- return res
54
- output_path = res.inputPath.with_suffix('.ans')
55
- if output_path.is_file():
56
- res.outputPath = output_path
57
- return res
58
-
59
-
60
44
  def _copy_testcase_over(
61
45
  testcase: Testcase,
62
46
  dest: Testcase,
63
47
  ):
64
- testcase = _fill_output_for_defined_testcase(testcase)
48
+ testcase = fill_output_for_defined_testcase(testcase)
65
49
  dest.inputPath.parent.mkdir(parents=True, exist_ok=True)
66
50
  shutil.copy(
67
51
  str(testcase.inputPath),
@@ -90,230 +74,6 @@ def get_call_from_string(call_str: str) -> GeneratorCall:
90
74
  return GeneratorCall(name=name, args=args)
91
75
 
92
76
 
93
- def _run_generator_script(testcase: TestcaseSubgroup) -> str:
94
- assert testcase.generatorScript is not None
95
-
96
- cacher = package.get_file_cacher()
97
-
98
- if not testcase.generatorScript.path.is_file():
99
- console.console.print(
100
- f'[error]Generator script not found: [item]{testcase.generatorScript.path}[/item][/error]'
101
- )
102
- raise typer.Exit(1)
103
-
104
- script_digest = DigestHolder()
105
- if testcase.generatorScript.path.suffix == '.txt':
106
- script_digest.value = cacher.put_file_from_path(testcase.generatorScript.path)
107
- else:
108
- try:
109
- compiled_digest = compile_item(testcase.generatorScript)
110
- except:
111
- console.console.print(
112
- f'[error]Failed compiling generator script for group [item]{testcase.name}[/item].[/error]'
113
- )
114
- raise
115
-
116
- run_stderr = DigestHolder()
117
- run_log = run_item(
118
- testcase.generatorScript,
119
- DigestOrSource.create(compiled_digest),
120
- stdout=DigestOrDest.create(script_digest),
121
- stderr=DigestOrDest.create(run_stderr),
122
- )
123
-
124
- if run_log is None or run_log.exitcode != 0:
125
- console.console.print(
126
- f'Could not run generator script for group {testcase.name}'
127
- )
128
- if run_log is not None:
129
- console.console.print(
130
- f'[error]Summary:[/error] {run_log.get_summary()}'
131
- )
132
- if run_stderr.value is not None:
133
- console.console.print('[error]Stderr:[/error]')
134
- console.console.print(
135
- package.get_digest_as_string(run_stderr.value) or ''
136
- )
137
- raise typer.Exit(1)
138
-
139
- assert script_digest.value
140
- script = cacher.get_file_content(script_digest.value).decode()
141
- return script
142
-
143
-
144
- def _extract_script_lines(script: str) -> Iterable[Tuple[str, str, int]]:
145
- lines = script.splitlines()
146
- for i, line in enumerate(lines):
147
- line = line.strip()
148
- if not line:
149
- continue
150
- if line.startswith('#'):
151
- continue
152
- yield shlex.split(line)[0], shlex.join(shlex.split(line)[1:]), i + 1
153
-
154
-
155
- class GeneratorScriptEntry(BaseModel):
156
- path: pathlib.Path
157
- line: int
158
-
159
-
160
- class GenerationMetadata(BaseModel):
161
- copied_to: Testcase
162
-
163
- copied_from: Optional[Testcase] = None
164
- generator_call: Optional[GeneratorCall] = None
165
- generator_script: Optional[GeneratorScriptEntry] = None
166
-
167
-
168
- class GenerationTestcaseEntry(BaseModel):
169
- group_entry: TestcaseEntry
170
- subgroup_entry: TestcaseEntry
171
-
172
- metadata: GenerationMetadata
173
-
174
-
175
- class TestcaseVisitor(abc.ABC):
176
- @abc.abstractmethod
177
- def visit(self, entry: GenerationTestcaseEntry):
178
- pass
179
-
180
- def should_visit_group(self, group_name: str) -> bool:
181
- return True
182
-
183
- def should_visit_subgroup(self, subgroup_path: str) -> bool:
184
- return True
185
-
186
- def should_visit_generator_scripts(
187
- self, group_name: str, subgroup_path: str
188
- ) -> bool:
189
- return True
190
-
191
-
192
- class TestcaseGroupVisitor(TestcaseVisitor):
193
- def __init__(self, groups: Optional[Set[str]] = None):
194
- self.groups = groups
195
-
196
- def should_visit_group(self, group_name: str) -> bool:
197
- return self.groups is None or group_name in self.groups
198
-
199
-
200
- def run_testcase_visitor(visitor: TestcaseVisitor):
201
- pkg = package.find_problem_package_or_die()
202
-
203
- def _explore_subgroup(
204
- subgroup: TestcaseSubgroup, subgroup_index: Optional[int], prefix: List[str]
205
- ):
206
- assert prefix and len(prefix) >= 1 and len(prefix) <= 2
207
- group_path = prefix[0]
208
- subgroup_path = '/'.join(prefix)
209
- if not visitor.should_visit_subgroup(subgroup_path):
210
- return
211
-
212
- def _entry(i: int) -> TestcaseEntry:
213
- return TestcaseEntry(group=group_path, index=i)
214
-
215
- def _sub_entry(i: int) -> TestcaseEntry:
216
- return TestcaseEntry(group=subgroup_path, index=i)
217
-
218
- def _copied_to(i: int) -> Testcase:
219
- group_fs_path = package.get_build_testgroup_path(group_path)
220
- group_prefix = ''
221
- if subgroup_index is not None:
222
- group_prefix = f'{subgroup_index}-'
223
- if len(prefix) == 2:
224
- group_prefix += f'{prefix[1]}-'
225
- return Testcase(
226
- inputPath=_get_group_input(group_fs_path, group_prefix, i),
227
- outputPath=_get_group_output(group_fs_path, group_prefix, i),
228
- )
229
-
230
- # Go through testcases.
231
- i = 0
232
- # Individual testcases.
233
- for tc in subgroup.testcases or []:
234
- visitor.visit(
235
- GenerationTestcaseEntry(
236
- group_entry=_entry(i),
237
- subgroup_entry=_sub_entry(i),
238
- metadata=GenerationMetadata(
239
- copied_from=_fill_output_for_defined_testcase(tc),
240
- copied_to=_copied_to(i),
241
- ),
242
- )
243
- )
244
- i += 1
245
-
246
- # Glob testcases.
247
- if subgroup.testcaseGlob:
248
- matched_inputs = sorted(PosixPath().glob(subgroup.testcaseGlob))
249
-
250
- for input_path in matched_inputs:
251
- if not input_path.is_file() or input_path.suffix != '.in':
252
- continue
253
-
254
- tc = Testcase(inputPath=input_path)
255
- visitor.visit(
256
- GenerationTestcaseEntry(
257
- group_entry=_entry(i),
258
- subgroup_entry=_sub_entry(i),
259
- metadata=GenerationMetadata(
260
- copied_from=_fill_output_for_defined_testcase(tc),
261
- copied_to=_copied_to(i),
262
- ),
263
- )
264
- )
265
- i += 1
266
-
267
- # Single generators.
268
- for generator_call in subgroup.generators:
269
- visitor.visit(
270
- GenerationTestcaseEntry(
271
- group_entry=_entry(i),
272
- subgroup_entry=_sub_entry(i),
273
- metadata=GenerationMetadata(
274
- generator_call=generator_call,
275
- copied_to=_copied_to(i),
276
- ),
277
- )
278
- )
279
- i += 1
280
-
281
- if not visitor.should_visit_generator_scripts(group_path, subgroup_path):
282
- return
283
-
284
- # Run generator script.
285
- if subgroup.generatorScript is not None:
286
- script = _run_generator_script(subgroup)
287
-
288
- # Run each line from generator script.
289
- for generator_name, args, line_number in _extract_script_lines(script):
290
- call = GeneratorCall(name=generator_name, args=args)
291
- visitor.visit(
292
- GenerationTestcaseEntry(
293
- group_entry=_entry(i),
294
- subgroup_entry=_sub_entry(i),
295
- metadata=GenerationMetadata(
296
- generator_call=call,
297
- generator_script=GeneratorScriptEntry(
298
- path=subgroup.generatorScript.path,
299
- line=line_number,
300
- ),
301
- copied_to=_copied_to(i),
302
- ),
303
- )
304
- )
305
- i += 1
306
-
307
- for group in pkg.testcases:
308
- if not visitor.should_visit_group(group.name):
309
- continue
310
-
311
- _explore_subgroup(group, 0 if group.subgroups else None, [group.name])
312
-
313
- for i, subgroup in enumerate(group.subgroups):
314
- _explore_subgroup(subgroup, i + 1, [group.name, subgroup.name])
315
-
316
-
317
77
  def _get_necessary_generators_for_groups(
318
78
  groups: Optional[Set[str]] = None,
319
79
  ) -> Set[str]:
@@ -439,14 +199,14 @@ def generate_standalone(
439
199
  _, validator_digest = validator_tp
440
200
  if progress:
441
201
  progress.update('Validating test...')
442
- ok, message, *_ = validators.validate_test(
202
+ validation_info = validators.validate_one_off(
443
203
  spec.copied_to.inputPath,
444
204
  validator,
445
205
  validator_digest,
446
206
  )
447
- if not ok:
207
+ if not validation_info.ok:
448
208
  _print_error_header('failed validating testcase.')
449
- console.console.print(f'[error]Message:[/error] {message}')
209
+ console.console.print(f'[error]Message:[/error] {validation_info.message}')
450
210
  console.console.print(
451
211
  f'Testcase written at [item]{spec.copied_to.inputPath}[/item]'
452
212
  )
@@ -552,67 +312,6 @@ def generate_output_for_testcase(
552
312
  raise typer.Exit(1)
553
313
 
554
314
 
555
- def extract_generation_testcases(
556
- entries: List[TestcaseEntry],
557
- ) -> List[GenerationTestcaseEntry]:
558
- # TODO: support subgroups.
559
- groups = set(entry.group for entry in entries)
560
- entry_keys = set(entry.key() for entry in entries)
561
-
562
- res: List[GenerationTestcaseEntry] = []
563
-
564
- class ExtractGenerationTestcasesVisitor(TestcaseVisitor):
565
- def should_visit_group(self, group_name: str) -> bool:
566
- return group_name in groups
567
-
568
- def visit(self, entry: GenerationTestcaseEntry):
569
- # TODO: support subgroups.
570
- if entry.group_entry.key() not in entry_keys:
571
- return
572
- res.append(entry)
573
-
574
- run_testcase_visitor(ExtractGenerationTestcasesVisitor())
575
- return res
576
-
577
-
578
- def extract_generation_testcases_from_groups(
579
- groups: Optional[Set[str]] = None,
580
- ) -> List[GenerationTestcaseEntry]:
581
- res: List[GenerationTestcaseEntry] = []
582
-
583
- class ExtractGenerationTestcasesVisitor(TestcaseGroupVisitor):
584
- def visit(self, entry: GenerationTestcaseEntry):
585
- res.append(entry)
586
-
587
- run_testcase_visitor(ExtractGenerationTestcasesVisitor(groups))
588
- return res
589
-
590
-
591
- def extract_generation_testcases_from_patterns(
592
- patterns: List[TestcasePattern],
593
- ) -> List[GenerationTestcaseEntry]:
594
- res: List[GenerationTestcaseEntry] = []
595
-
596
- class ExtractGenerationTestcasesVisitor(TestcaseVisitor):
597
- def should_visit_group(self, group_name: str) -> bool:
598
- return any(pattern.intersecting_group(group_name) for pattern in patterns)
599
-
600
- def should_visit_subgroup(self, subgroup_path: str) -> bool:
601
- return any(
602
- pattern.intersecting_group(subgroup_path) for pattern in patterns
603
- )
604
-
605
- def visit(self, entry: GenerationTestcaseEntry):
606
- if not any(
607
- pattern.match(entry.group_entry) for pattern in patterns
608
- ) and not any(pattern.match(entry.subgroup_entry) for pattern in patterns):
609
- return
610
- res.append(entry)
611
-
612
- run_testcase_visitor(ExtractGenerationTestcasesVisitor())
613
- return res
614
-
615
-
616
315
  def generate_outputs_for_testcases(
617
316
  entries: List[TestcaseEntry],
618
317
  progress: Optional[StatusProgress] = None,
@@ -4,10 +4,10 @@ import pytest
4
4
 
5
5
  from rbx.box import package
6
6
  from rbx.box.generators import (
7
- extract_generation_testcases_from_groups,
8
7
  generate_outputs_for_testcases,
9
8
  generate_testcases,
10
9
  )
10
+ from rbx.box.testcase_extractors import extract_generation_testcases_from_groups
11
11
  from rbx.testing_utils import print_directory_tree
12
12
 
13
13
 
@@ -37,6 +37,7 @@ from rbx.box import (
37
37
  validators,
38
38
  )
39
39
  from rbx.box.contest import main as contest
40
+ from rbx.box.contest.contest_package import find_contest_yaml
40
41
  from rbx.box.environment import VerificationLevel, get_environment_path
41
42
  from rbx.box.packaging import main as packaging
42
43
  from rbx.box.testcases import main as testcases
@@ -448,6 +449,15 @@ def create(
448
449
  Optional[str], typer.Option(help='Preset to use when creating the problem.')
449
450
  ] = None,
450
451
  ):
452
+ if find_contest_yaml() is not None:
453
+ console.console.print(
454
+ '[error]Cannot [item]rbx create[/item] a problem inside a contest.[/error]'
455
+ )
456
+ console.console.print(
457
+ '[error]Instead, use [item]rbx contest add[/item] to add a problem to a contest.[/error]'
458
+ )
459
+ raise typer.Exit(1)
460
+
451
461
  if preset is not None:
452
462
  creation.create(name, preset=preset)
453
463
  return
@@ -121,8 +121,8 @@ def save_package(
121
121
  problem_yaml_path.write_text(utils.model_to_yaml(package))
122
122
 
123
123
 
124
- def get_ruyaml() -> Tuple[ruyaml.YAML, ruyaml.Any]:
125
- problem_yaml_path = find_problem_yaml()
124
+ def get_ruyaml(root: pathlib.Path = pathlib.Path()) -> Tuple[ruyaml.YAML, ruyaml.Any]:
125
+ problem_yaml_path = find_problem_yaml(root)
126
126
  if problem_yaml_path is None:
127
127
  console.console.print(
128
128
  f'Problem not found in {pathlib.Path().absolute()}', style='error'
@@ -232,6 +232,13 @@ A generator script to call to generate testcases for this group.
232
232
  """,
233
233
  )
234
234
 
235
+ extraValidators: List[CodeItem] = Field(
236
+ default=[],
237
+ description="""
238
+ A list of extra validators to use to validate the testcases of this subgroup.
239
+ """,
240
+ )
241
+
235
242
  @model_validator(mode='after')
236
243
  def check_oneof(self) -> 'TestcaseSubgroup':
237
244
  _check_oneof(
@@ -260,7 +267,7 @@ A list of test subgroups to define for this group.
260
267
  default=None,
261
268
  description="""
262
269
  A validator to use to validate the testcases of this group.
263
- If not specified, will use the package-level validator.
270
+ If specified, will use this validator instead of the package-level validator.
264
271
  Useful in cases where the constraints vary across test groups.
265
272
  """,
266
273
  )
@@ -29,7 +29,6 @@ from rbx.box.formatting import get_formatted_memory, get_formatted_time
29
29
  from rbx.box.generators import (
30
30
  GenerationMetadata,
31
31
  expand_generator_call,
32
- extract_generation_testcases,
33
32
  generate_output_for_testcase,
34
33
  generate_standalone,
35
34
  )
@@ -42,6 +41,7 @@ from rbx.box.schema import (
42
41
  Testcase,
43
42
  TestcaseGroup,
44
43
  )
44
+ from rbx.box.testcase_extractors import extract_generation_testcases
45
45
  from rbx.box.testcase_utils import TestcaseEntry, find_built_testcases
46
46
  from rbx.grading.steps import (
47
47
  DigestOrDest,
@@ -5,7 +5,6 @@ import pytest
5
5
 
6
6
  from rbx.box.environment import VerificationLevel
7
7
  from rbx.box.generators import (
8
- extract_generation_testcases_from_groups,
9
8
  generate_outputs_for_testcases,
10
9
  generate_testcases,
11
10
  )
@@ -13,6 +12,7 @@ from rbx.box.solutions import (
13
12
  convert_list_of_solution_evaluations_to_dict,
14
13
  run_solutions,
15
14
  )
15
+ from rbx.box.testcase_extractors import extract_generation_testcases_from_groups
16
16
  from rbx.grading.steps import Outcome
17
17
 
18
18