rbx.cp 0.5.24__tar.gz → 0.5.26__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 (168) hide show
  1. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/PKG-INFO +1 -1
  2. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/pyproject.toml +1 -1
  3. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/code.py +1 -1
  4. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/contest/main.py +9 -6
  5. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/main.py +75 -34
  6. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/presets/__init__.py +37 -10
  7. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/solutions.py +55 -9
  8. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/validators.py +15 -3
  9. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/console.py +12 -0
  10. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/grading/steps.py +11 -4
  11. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/LICENSE +0 -0
  12. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/README.md +0 -0
  13. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/__init__.py +0 -0
  14. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/annotations.py +0 -0
  15. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/autoenum.py +0 -0
  16. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/__init__.py +0 -0
  17. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/builder.py +0 -0
  18. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/cd.py +0 -0
  19. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/checkers.py +0 -0
  20. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/compile.py +0 -0
  21. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/conftest.py +0 -0
  22. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/contest/__init__.py +0 -0
  23. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/contest/build_contest_statements.py +0 -0
  24. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/contest/contest_package.py +0 -0
  25. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/contest/contest_utils.py +0 -0
  26. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/contest/schema.py +0 -0
  27. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/contest/statements.py +0 -0
  28. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/creation.py +0 -0
  29. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/deferred.py +0 -0
  30. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/download.py +0 -0
  31. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/environment.py +0 -0
  32. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/extensions.py +0 -0
  33. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/generators.py +0 -0
  34. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/generators_test.py +0 -0
  35. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/package.py +0 -0
  36. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/packaging/boca/extension.py +0 -0
  37. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/packaging/boca/packager.py +0 -0
  38. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/packaging/contest_main.py +0 -0
  39. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/packaging/main.py +0 -0
  40. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/packaging/packager.py +0 -0
  41. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/packaging/polygon/packager.py +0 -0
  42. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/packaging/polygon/test.py +0 -0
  43. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/packaging/polygon/xml_schema.py +0 -0
  44. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/presets/fetch.py +0 -0
  45. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/presets/lock_schema.py +0 -0
  46. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/presets/schema.py +0 -0
  47. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/sanitizers/warning_stack.py +0 -0
  48. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/schema.py +0 -0
  49. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/setter_config.py +0 -0
  50. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/solutions_test.py +0 -0
  51. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/statements/__init__.py +0 -0
  52. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/statements/build_statements.py +0 -0
  53. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/statements/builders.py +0 -0
  54. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/statements/joiners.py +0 -0
  55. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/statements/latex.py +0 -0
  56. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/statements/latex_jinja.py +0 -0
  57. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/statements/schema.py +0 -0
  58. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/stresses.py +0 -0
  59. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/stressing/__init__.py +0 -0
  60. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/stressing/finder_parser.py +0 -0
  61. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/stressing/generator_parser.py +0 -0
  62. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/testcases.py +0 -0
  63. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/ui/__init__.py +0 -0
  64. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/ui/captured_log.py +0 -0
  65. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/ui/css/app.tcss +0 -0
  66. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/ui/main.py +0 -0
  67. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/ui/run.py +0 -0
  68. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/box/validators_test.py +0 -0
  69. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/checker.py +0 -0
  70. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/clone.py +0 -0
  71. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/config.py +0 -0
  72. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/conftest.py +0 -0
  73. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/create.py +0 -0
  74. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/edit.py +0 -0
  75. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/grading/__init__.py +0 -0
  76. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/grading/caching.py +0 -0
  77. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/grading/conftest.py +0 -0
  78. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/grading/judge/__init__.py +0 -0
  79. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/grading/judge/cacher.py +0 -0
  80. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/grading/judge/digester.py +0 -0
  81. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/grading/judge/sandbox.py +0 -0
  82. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/grading/judge/sandboxes/__init__.py +0 -0
  83. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/grading/judge/sandboxes/isolate.py +0 -0
  84. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/grading/judge/sandboxes/stupid_sandbox.py +0 -0
  85. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/grading/judge/sandboxes/timeit.py +0 -0
  86. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/grading/judge/storage.py +0 -0
  87. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/grading/judge/test.py +0 -0
  88. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/grading/judge/testiso.py +0 -0
  89. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/grading/steps_with_caching.py +0 -0
  90. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/grading/steps_with_caching_run_test.py +0 -0
  91. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/grading_utils.py +0 -0
  92. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/hydration.py +0 -0
  93. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/main.py +0 -0
  94. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/metadata.py +0 -0
  95. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/providers/__init__.py +0 -0
  96. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/providers/codeforces.py +0 -0
  97. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/providers/provider.py +0 -0
  98. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/checkers/boilerplate.cpp +0 -0
  99. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/default_config.json +0 -0
  100. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/default_setter_config.mac.yml +0 -0
  101. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/default_setter_config.yml +0 -0
  102. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/envs/default.rbx.yml +0 -0
  103. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/envs/isolate.rbx.yml +0 -0
  104. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/packagers/boca/checker.sh +0 -0
  105. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/packagers/boca/compare +0 -0
  106. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/packagers/boca/compile/c +0 -0
  107. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/packagers/boca/compile/cc +0 -0
  108. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/packagers/boca/compile/cpp +0 -0
  109. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/packagers/boca/compile/java +0 -0
  110. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/packagers/boca/compile/kt +0 -0
  111. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/packagers/boca/compile/pas +0 -0
  112. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/packagers/boca/compile/py2 +0 -0
  113. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/packagers/boca/compile/py3 +0 -0
  114. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/packagers/boca/run/c +0 -0
  115. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/packagers/boca/run/cc +0 -0
  116. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/packagers/boca/run/cpp +0 -0
  117. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/packagers/boca/run/java +0 -0
  118. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/packagers/boca/run/kt +0 -0
  119. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/packagers/boca/run/py2 +0 -0
  120. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/packagers/boca/run/py3 +0 -0
  121. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/presets/default/contest/contest.rbx.yml +0 -0
  122. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/presets/default/contest/statement/contest.rbx.tex +0 -0
  123. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/presets/default/contest/statement/olymp.sty +0 -0
  124. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/presets/default/contest/statement/template.rbx.tex +0 -0
  125. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/presets/default/preset.rbx.yml +0 -0
  126. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/presets/default/problem/.gitignore +0 -0
  127. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/presets/default/problem/gen.cpp +0 -0
  128. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/presets/default/problem/problem.rbx.yml +0 -0
  129. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/presets/default/problem/random.py +0 -0
  130. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/presets/default/problem/random.txt +0 -0
  131. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/presets/default/problem/sols/main.cpp +0 -0
  132. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/presets/default/problem/sols/slow.cpp +0 -0
  133. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/presets/default/problem/sols/wa.cpp +0 -0
  134. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/presets/default/problem/statement/olymp.sty +0 -0
  135. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/presets/default/problem/statement/projecao.png +0 -0
  136. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/presets/default/problem/statement/statement.rbx.tex +0 -0
  137. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/presets/default/problem/statement/template.rbx.tex +0 -0
  138. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/presets/default/problem/tests/samples/000.in +0 -0
  139. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/presets/default/problem/tests/samples/001.in +0 -0
  140. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/presets/default/problem/validator.cpp +0 -0
  141. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/presets/default/problem/wcmp.cpp +0 -0
  142. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/resources/templates/template.cpp +0 -0
  143. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/run.py +0 -0
  144. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/schema.py +0 -0
  145. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/submit.py +0 -0
  146. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/submitors/__init__.py +0 -0
  147. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/submitors/codeforces.py +0 -0
  148. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/submitors/submitor.py +0 -0
  149. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/test.py +0 -0
  150. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/testcase.py +0 -0
  151. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/testcase_rendering.py +0 -0
  152. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/testdata/box1/gen1.cpp +0 -0
  153. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/testdata/box1/gen2.cpp +0 -0
  154. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/testdata/box1/genScript.py +0 -0
  155. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/testdata/box1/hard-tle.sol.cpp +0 -0
  156. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/testdata/box1/ole.cpp +0 -0
  157. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/testdata/box1/problem.rbx.yml +0 -0
  158. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/testdata/box1/re.sol.cpp +0 -0
  159. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/testdata/box1/sol.cpp +0 -0
  160. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/testdata/box1/tests/1.in +0 -0
  161. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/testdata/box1/tle-and-incorrect.sol.cpp +0 -0
  162. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/testdata/box1/tle.sol.cpp +0 -0
  163. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/testdata/box1/validator.cpp +0 -0
  164. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/testdata/box1/wa.sol.cpp +0 -0
  165. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/testdata/caching/executable.py +0 -0
  166. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/testdata/compatible +0 -0
  167. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/testing_utils.py +0 -0
  168. {rbx_cp-0.5.24 → rbx_cp-0.5.26}/rbx/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: rbx.cp
3
- Version: 0.5.24
3
+ Version: 0.5.26
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.24"
3
+ version = "0.5.26"
4
4
  description = ""
5
5
  packages = [
6
6
  {include = "rbx"}
@@ -75,7 +75,7 @@ def is_executable_sanitized(executable: DigestOrSource) -> bool:
75
75
 
76
76
  def add_sanitizer_flags_to_command(command: str) -> str:
77
77
  if is_cxx_command(command):
78
- return command + ' -fsanitize=address,undefined -fno-omit-frame-pointer'
78
+ return command + ' -fsanitize=address,undefined -fno-omit-frame-pointer -g'
79
79
  return command
80
80
 
81
81
 
@@ -147,12 +147,15 @@ def add(path: str, short_name: str, preset: Optional[str] = None):
147
147
 
148
148
  ru, contest = contest_package.get_ruyaml()
149
149
 
150
- contest['problems'].append(
151
- {
152
- 'short_name': short_name,
153
- 'path': path,
154
- }
155
- )
150
+ item = {
151
+ 'short_name': short_name,
152
+ 'path': path,
153
+ }
154
+ if 'problems' not in contest:
155
+ contest['problems'] = [item]
156
+ else:
157
+ contest['problems'].append(item)
158
+
156
159
  dest = find_contest_yaml()
157
160
  assert dest is not None
158
161
  utils.save_ruyaml(dest, ru, contest)
@@ -42,6 +42,7 @@ from rbx.box.solutions import (
42
42
  estimate_time_limit,
43
43
  get_exact_matching_solutions,
44
44
  get_matching_solutions,
45
+ pick_solutions,
45
46
  print_run_report,
46
47
  run_and_print_interactive_solutions,
47
48
  run_solutions,
@@ -143,6 +144,13 @@ def run(
143
144
  '-s',
144
145
  help='Whether to compile the solutions with sanitizers enabled.',
145
146
  ),
147
+ choice: bool = typer.Option(
148
+ False,
149
+ '--choice',
150
+ '--choose',
151
+ '-c',
152
+ help='Whether to pick solutions interactively.',
153
+ ),
146
154
  ):
147
155
  main_solution = package.get_main_solution()
148
156
  if check and main_solution is None:
@@ -174,31 +182,37 @@ def run(
174
182
  if override_tl is None:
175
183
  raise typer.Exit(1)
176
184
 
177
- with utils.StatusProgress('Running solutions...') as s:
178
- if sanitized:
179
- console.console.print(
180
- '[warning]Sanitizers are running, so the time limit for the problem will be dropped, '
181
- 'and the environment default time limit will be used instead.[/warning]'
182
- )
185
+ if sanitized:
186
+ console.console.print(
187
+ '[warning]Sanitizers are running, so the time limit for the problem will be dropped, '
188
+ 'and the environment default time limit will be used instead.[/warning]'
189
+ )
190
+
191
+ tracked_solutions = None
192
+ if outcome is not None:
193
+ tracked_solutions = {
194
+ str(solution.path)
195
+ for solution in get_matching_solutions(ExpectedOutcome(outcome))
196
+ }
197
+ if solution:
198
+ tracked_solutions = {solution}
183
199
 
184
- tracked_solutions = None
185
- if outcome is not None:
186
- tracked_solutions = {
187
- str(solution.path)
188
- for solution in get_matching_solutions(ExpectedOutcome(outcome))
189
- }
190
- if solution:
191
- tracked_solutions = {solution}
200
+ if choice:
201
+ tracked_solutions = set(pick_solutions(tracked_solutions))
202
+ if not tracked_solutions:
203
+ console.console.print('[error]No solutions selected. Exiting.[/error]')
204
+ raise typer.Exit(1)
192
205
 
193
- if sanitized and tracked_solutions is None:
194
- console.console.print(
195
- '[warning]Sanitizers are running, and no solutions were specified to run. Will only run [item]ACCEPTED[/item] solutions.'
196
- )
197
- tracked_solutions = {
198
- str(solution.path)
199
- for solution in get_exact_matching_solutions(ExpectedOutcome.ACCEPTED)
200
- }
206
+ if sanitized and tracked_solutions is None:
207
+ console.console.print(
208
+ '[warning]Sanitizers are running, and no solutions were specified to run. Will only run [item]ACCEPTED[/item] solutions.'
209
+ )
210
+ tracked_solutions = {
211
+ str(solution.path)
212
+ for solution in get_exact_matching_solutions(ExpectedOutcome.ACCEPTED)
213
+ }
201
214
 
215
+ with utils.StatusProgress('Running solutions...') as s:
202
216
  solution_result = run_solutions(
203
217
  progress=s,
204
218
  tracked_solutions=tracked_solutions,
@@ -338,6 +352,13 @@ def irun(
338
352
  '-s',
339
353
  help='Whether to compile the solutions with sanitizers enabled.',
340
354
  ),
355
+ choice: bool = typer.Option(
356
+ False,
357
+ '--choice',
358
+ '--choose',
359
+ '-c',
360
+ help='Whether to pick solutions interactively.',
361
+ ),
341
362
  ):
342
363
  if not print:
343
364
  console.console.print(
@@ -364,6 +385,13 @@ def irun(
364
385
  }
365
386
  if solution:
366
387
  tracked_solutions = {solution}
388
+
389
+ if choice:
390
+ tracked_solutions = set(pick_solutions(tracked_solutions))
391
+ if not tracked_solutions:
392
+ console.console.print('[error]No solutions selected. Exiting.[/error]')
393
+ raise typer.Exit(1)
394
+
367
395
  if sanitized and tracked_solutions is None:
368
396
  console.console.print(
369
397
  '[warning]Sanitizers are running, and no solutions were specified to run. Will only run [item]ACCEPTED[/item] solutions.'
@@ -373,18 +401,20 @@ def irun(
373
401
  for solution in get_exact_matching_solutions(ExpectedOutcome.ACCEPTED)
374
402
  }
375
403
 
376
- asyncio.run(
377
- run_and_print_interactive_solutions(
378
- tracked_solutions=tracked_solutions,
379
- check=check,
380
- verification=VerificationLevel(verification),
381
- generator=generators.get_call_from_string(generator)
382
- if generator is not None
383
- else None,
384
- print=print,
385
- sanitized=sanitized,
404
+ with utils.StatusProgress('Running solutions...') as s:
405
+ asyncio.run(
406
+ run_and_print_interactive_solutions(
407
+ progress=s,
408
+ tracked_solutions=tracked_solutions,
409
+ check=check,
410
+ verification=VerificationLevel(verification),
411
+ generator=generators.get_call_from_string(generator)
412
+ if generator is not None
413
+ else None,
414
+ print=print,
415
+ sanitized=sanitized,
416
+ )
386
417
  )
387
- )
388
418
 
389
419
 
390
420
  @app.command('create, c', help='Create a new problem package.')
@@ -510,6 +540,8 @@ def stress(
510
540
  name=testgroup, generatorScript=CodeItem(path=new_script_path)
511
541
  )
512
542
  ru, problem_yml = package.get_ruyaml()
543
+ if 'testcases' not in problem_yml:
544
+ problem_yml['testcases'] = []
513
545
  problem_yml['testcases'].append(
514
546
  {
515
547
  'name': testgroup,
@@ -551,7 +583,10 @@ def stress(
551
583
  @app.command('compile', help='Compile an asset given its path.')
552
584
  @package.within_problem
553
585
  def compile_command(
554
- path: Annotated[str, typer.Argument(help='Path to the asset to compile.')],
586
+ path: Annotated[
587
+ Optional[str],
588
+ typer.Argument(help='Path to the asset to compile.'),
589
+ ] = None,
555
590
  sanitized: bool = typer.Option(
556
591
  False,
557
592
  '--sanitized',
@@ -565,6 +600,12 @@ def compile_command(
565
600
  help='Whether to compile the asset with warnings enabled.',
566
601
  ),
567
602
  ):
603
+ if path is None:
604
+ path = questionary.path("What's the path to your asset?").ask()
605
+ if path is None:
606
+ console.console.print('[error]No path specified.[/error]')
607
+ raise typer.Exit(1)
608
+
568
609
  compile.any(path, sanitized, warnings)
569
610
 
570
611
 
@@ -68,13 +68,14 @@ def _find_nested_preset(root: pathlib.Path) -> Optional[pathlib.Path]:
68
68
 
69
69
 
70
70
  def _find_local_preset(root: pathlib.Path) -> Optional[pathlib.Path]:
71
+ original_root = root
71
72
  root = root.resolve()
72
73
  problem_yaml_path = root / '.local.rbx' / 'preset.rbx.yml'
73
74
  while root != pathlib.PosixPath('/') and not problem_yaml_path.is_file():
74
75
  root = root.parent
75
76
  problem_yaml_path = root / '.local.rbx' / 'preset.rbx.yml'
76
77
  if not problem_yaml_path.is_file():
77
- return _find_nested_preset(root)
78
+ return _find_nested_preset(original_root)
78
79
  return problem_yaml_path.parent
79
80
 
80
81
 
@@ -326,6 +327,13 @@ def _install(root: pathlib.Path = pathlib.Path(), force: bool = False):
326
327
  console.console.print('[error]Naming a preset "local" is prohibited.[/error]')
327
328
 
328
329
  console.console.print(f'Installing preset [item]{preset.name}[/item]...')
330
+ installation_path = get_preset_installation_path(preset.name)
331
+
332
+ if root.resolve().is_relative_to(installation_path.resolve()):
333
+ console.console.print(
334
+ '[error]Current folder is nested into the preset installation path, cannot install it.[/error]'
335
+ )
336
+ raise typer.Exit(1)
329
337
 
330
338
  if preset.env is not None:
331
339
  console.console.print(
@@ -346,7 +354,6 @@ def _install(root: pathlib.Path = pathlib.Path(), force: bool = False):
346
354
  shutil.copyfile(str(root / preset.env), get_environment_path(preset.name))
347
355
 
348
356
  console.console.print(f'[item]{preset.name}[/item]: Copying preset folder...')
349
- installation_path = get_preset_installation_path(preset.name)
350
357
  installation_path.parent.mkdir(parents=True, exist_ok=True)
351
358
  if installation_path.exists():
352
359
  res = force or rich.prompt.Confirm.ask(
@@ -412,7 +419,7 @@ def generate_lock(
412
419
  )
413
420
 
414
421
 
415
- def _sync(update: bool = False):
422
+ def _sync(try_update: bool = False):
416
423
  preset_lock = get_preset_lock()
417
424
  if preset_lock is None:
418
425
  console.console.print(
@@ -423,18 +430,17 @@ def _sync(update: bool = False):
423
430
  )
424
431
  raise typer.Exit(1)
425
432
 
426
- should_update = update and preset_lock.uri is not None
433
+ should_update = try_update and preset_lock.uri is not None
427
434
  installed_preset = get_installed_preset_or_null(preset_lock.preset_name)
428
435
  if installed_preset is None:
429
- if not update or preset_lock.uri is None:
436
+ if not try_update or preset_lock.uri is None:
430
437
  console.console.print(
431
438
  f'[error]Preset [item]{preset_lock.preset_name}[/item] is not installed. Install it before trying to update.'
432
439
  )
433
440
  raise typer.Exit(1)
434
- should_update = True
435
-
436
- if should_update:
437
- install(uri=preset_lock.uri)
441
+ install(preset_lock.uri)
442
+ elif should_update:
443
+ update(preset_lock.name)
438
444
 
439
445
  _copy_updated_assets(
440
446
  preset_lock.preset_name,
@@ -477,6 +483,13 @@ def update(
477
483
  presets = [name]
478
484
 
479
485
  for preset_name in presets:
486
+ if preset_name == LOCAL:
487
+ if not questionary.confirm(
488
+ 'Updating local preset will remove all custom changes you made to the preset.',
489
+ default=False,
490
+ ).ask():
491
+ continue
492
+
480
493
  preset = get_installed_preset_or_null(preset_name)
481
494
  if preset is None:
482
495
  console.console.print(
@@ -490,6 +503,20 @@ def update(
490
503
  continue
491
504
  install_from_remote(preset.fetch_info, force=True)
492
505
 
506
+ if preset_name == LOCAL:
507
+ # Get global path to the preset.
508
+ preset_path = get_preset_installation_path(preset.name)
509
+ dest_path = '.local.rbx'
510
+ shutil.rmtree(dest_path, ignore_errors=True)
511
+ shutil.copytree(preset_path, dest_path)
512
+ console.console.print(
513
+ '[success]Local preset updated successfully.[/success]'
514
+ )
515
+ else:
516
+ console.console.print(
517
+ f'[success]Preset [item]{preset_name}[/item] updated successfully.[/success]'
518
+ )
519
+
493
520
 
494
521
  @app.command(
495
522
  'sync',
@@ -507,7 +534,7 @@ def sync(
507
534
  ] = False,
508
535
  ):
509
536
  _check_is_valid_package()
510
- _sync(update=update)
537
+ _sync(try_update=update)
511
538
 
512
539
 
513
540
  @app.command(
@@ -7,11 +7,13 @@ import shutil
7
7
  from collections.abc import Iterator
8
8
  from typing import Dict, Iterable, List, Optional, Set, Tuple
9
9
 
10
+ import questionary
10
11
  import rich
11
12
  import rich.live
12
13
  import rich.markup
13
14
  import rich.table
14
15
  import rich.text
16
+ import typer
15
17
  from pydantic import BaseModel
16
18
 
17
19
  from rbx import console
@@ -410,6 +412,7 @@ def run_solutions(
410
412
 
411
413
 
412
414
  def _run_interactive_solutions(
415
+ progress: Optional[StatusProgress] = None,
413
416
  tracked_solutions: Optional[Set[str]] = None,
414
417
  verification: VerificationLevel = VerificationLevel.NONE,
415
418
  generator: Optional[GeneratorCall] = None,
@@ -423,7 +426,7 @@ def _run_interactive_solutions(
423
426
 
424
427
  checker_digest = checkers.compile_checker() if check else None
425
428
  compiled_solutions = compile_solutions(
426
- tracked_solutions=tracked_solutions, sanitized=sanitized
429
+ progress=progress, tracked_solutions=tracked_solutions, sanitized=sanitized
427
430
  )
428
431
 
429
432
  main_solution_digest = None
@@ -498,6 +501,7 @@ def _run_interactive_solutions(
498
501
 
499
502
 
500
503
  async def run_and_print_interactive_solutions(
504
+ progress: Optional[StatusProgress] = None,
501
505
  tracked_solutions: Optional[Set[str]] = None,
502
506
  verification: VerificationLevel = VerificationLevel.NONE,
503
507
  generator: Optional[GeneratorCall] = None,
@@ -507,6 +511,7 @@ async def run_and_print_interactive_solutions(
507
511
  ):
508
512
  pkg = package.find_problem_package_or_die()
509
513
  items = _run_interactive_solutions(
514
+ progress=progress,
510
515
  tracked_solutions=tracked_solutions,
511
516
  verification=verification,
512
517
  check=check,
@@ -515,6 +520,9 @@ async def run_and_print_interactive_solutions(
515
520
  print=print,
516
521
  )
517
522
 
523
+ if progress:
524
+ progress.stop()
525
+
518
526
  for item in items:
519
527
  sol = pkg.solutions[item.solution_index]
520
528
  _print_solution_header(sol, console.console, is_irun=True)
@@ -540,6 +548,31 @@ async def run_and_print_interactive_solutions(
540
548
  console.console.print()
541
549
 
542
550
 
551
+ def _get_solution_repr(sol: Solution) -> List[Tuple[str, str]]:
552
+ return [
553
+ ('', f'{str(sol.path)} '),
554
+ (f'fg:{sol.outcome.style().replace('lnumber', 'cyan')}', sol.outcome.name),
555
+ ]
556
+
557
+
558
+ def pick_solutions(tracked_solutions: Optional[Set[str]]) -> List[str]:
559
+ pkg = package.find_problem_package_or_die()
560
+ if tracked_solutions is None:
561
+ tracked_solutions = set(str(sol.path) for sol in pkg.solutions)
562
+
563
+ # Store in a separate list to maintain order with the package declaration.
564
+ choices = [
565
+ questionary.Choice(title=_get_solution_repr(sol), value=str(sol.path))
566
+ for sol in pkg.solutions
567
+ if str(sol.path) in tracked_solutions
568
+ ]
569
+
570
+ picked = questionary.checkbox('Select solutions', choices=choices).ask()
571
+ if picked is None:
572
+ raise typer.Abort()
573
+ return picked
574
+
575
+
543
576
  def get_outcome_style_verdict(outcome: Outcome) -> str:
544
577
  if outcome == Outcome.ACCEPTED:
545
578
  return 'green'
@@ -554,21 +587,25 @@ def get_outcome_style_verdict(outcome: Outcome) -> str:
554
587
  return 'magenta'
555
588
 
556
589
 
557
- def get_testcase_markup_verdict(eval: Evaluation) -> str:
590
+ def get_outcome_markup_verdict(outcome: Outcome) -> str:
558
591
  res = '✓'
559
- if eval.result.outcome != Outcome.ACCEPTED:
592
+ if outcome != Outcome.ACCEPTED:
560
593
  res = '✗'
561
- if eval.result.outcome == Outcome.TIME_LIMIT_EXCEEDED:
594
+ if outcome == Outcome.TIME_LIMIT_EXCEEDED:
562
595
  res = '⧖'
563
- if eval.result.outcome == Outcome.RUNTIME_ERROR:
596
+ if outcome == Outcome.RUNTIME_ERROR:
564
597
  res = '✗'
565
- style = get_outcome_style_verdict(eval.result.outcome)
598
+ style = get_outcome_style_verdict(outcome)
566
599
  res = f'[{style}]{res}[/{style}]'
600
+ return res
601
+
602
+
603
+ def get_testcase_markup_verdict(eval: Evaluation) -> str:
567
604
  # if eval.log.stdout_absolute_path:
568
605
  # output_path = eval.log.stdout_absolute_path.resolve()
569
606
  # output_link = f'file://{output_path}'
570
607
  # res = f'[link={output_link}]{res}[/link]'
571
- return res
608
+ return get_outcome_markup_verdict(eval.result.outcome)
572
609
 
573
610
 
574
611
  def _get_evals_time_in_ms(evals: List[Evaluation]) -> int:
@@ -632,6 +669,10 @@ def get_evals_formatted_memory(evals: List[Evaluation]) -> str:
632
669
  return get_formatted_memory(max_memory)
633
670
 
634
671
 
672
+ def get_worst_outcome(evals: List[Evaluation]) -> Outcome:
673
+ return Outcome.worst_outcome(eval.result.outcome for eval in evals)
674
+
675
+
635
676
  def _print_solution_outcome(
636
677
  solution: Solution,
637
678
  evals: List[Evaluation],
@@ -880,7 +921,11 @@ async def _render_detailed_group_table(
880
921
  solution, non_null_evals, verification
881
922
  )
882
923
  formatted_memory = get_evals_formatted_memory(non_null_evals)
883
- summary_row.append(('', '', formatted_time, '/', formatted_memory, ''))
924
+ worst_outcome = get_worst_outcome(non_null_evals)
925
+ verdict = get_outcome_markup_verdict(worst_outcome)
926
+ summary_row.append(
927
+ ('', verdict, formatted_time, '/', formatted_memory, '')
928
+ )
884
929
  padded_rows.append(summary_row)
885
930
 
886
931
  for row in _render_padded_rows(padded_rows):
@@ -1020,12 +1065,13 @@ async def print_run_report(
1020
1065
  )
1021
1066
  console.print()
1022
1067
 
1023
- ok = ok and _print_solution_outcome(
1068
+ cur_ok = _print_solution_outcome(
1024
1069
  solution,
1025
1070
  solution_evals,
1026
1071
  console,
1027
1072
  verification=verification,
1028
1073
  )
1074
+ ok = ok and cur_ok
1029
1075
  console.print()
1030
1076
 
1031
1077
  await _print_timing(console, result.skeleton, structured_evaluations)
@@ -262,10 +262,22 @@ def print_validation_report(infos: List[TestcaseValidationInfo]):
262
262
  if not _has_group_specific_validator():
263
263
  hit_bounds_per_group = {None: _merge_hit_bounds(hit_bounds_per_group.values())}
264
264
 
265
+ def _is_hit_bound_good(hit_bounds: HitBounds) -> bool:
266
+ return any(not v[0] or not v[1] for v in hit_bounds.values())
267
+
268
+ # Cleanup entries in hit bounds per group that are totally empty.
269
+ # Also skip samples.
270
+ hit_bounds_per_group = {
271
+ k: v
272
+ for k, v in hit_bounds_per_group.items()
273
+ if _is_hit_bound_good(v) and k != 'samples'
274
+ }
275
+
276
+ if not hit_bounds_per_group:
277
+ console.console.print('[info]No validation issues found.[/info]')
278
+ return
279
+
265
280
  for group, hit_bounds in hit_bounds_per_group.items():
266
- if group == 'samples':
267
- # Skip samples.
268
- continue
269
281
  if group is None:
270
282
  console.console.print('Hit bounds:')
271
283
  else:
@@ -1,5 +1,7 @@
1
1
  import sys
2
2
 
3
+ import rich
4
+ import rich.markup
3
5
  from rich.console import Console
4
6
  from rich.theme import Theme
5
7
 
@@ -25,3 +27,13 @@ def multiline_prompt(text: str) -> str:
25
27
  lines = sys.stdin.readlines()
26
28
  console.print()
27
29
  return ''.join(lines)
30
+
31
+
32
+ def render_from(r: rich.console.RenderableType) -> str:
33
+ with console.capture() as capture:
34
+ console.print(r)
35
+ return capture.get()
36
+
37
+
38
+ def render_from_markup(markup: str) -> str:
39
+ return render_from(rich.markup.render(markup))
@@ -6,7 +6,7 @@ import shutil
6
6
  import subprocess
7
7
  import sys
8
8
  from enum import Enum
9
- from typing import IO, Any, Dict, List, Optional, Tuple, Union
9
+ from typing import IO, Any, Dict, Iterable, List, Optional, Tuple, Union
10
10
 
11
11
  import typer
12
12
  from pydantic import BaseModel
@@ -24,13 +24,20 @@ MAX_STDOUT_LEN = 1024 * 1024 * 128 # 128 MB
24
24
  class Outcome(Enum):
25
25
  ACCEPTED = 'accepted'
26
26
  WRONG_ANSWER = 'wrong-answer'
27
- JUDGE_FAILED = 'judge-failed'
28
- RUNTIME_ERROR = 'runtime-error'
29
- TIME_LIMIT_EXCEEDED = 'time-limit-exceeded'
30
27
  MEMORY_LIMIT_EXCEEDED = 'memory-limit-exceeded'
28
+ TIME_LIMIT_EXCEEDED = 'time-limit-exceeded'
29
+ RUNTIME_ERROR = 'runtime-error'
31
30
  OUTPUT_LIMIT_EXCEEDED = 'output-limit-exceeded'
31
+ JUDGE_FAILED = 'judge-failed'
32
32
  INTERNAL_ERROR = 'internal-error'
33
33
 
34
+ @classmethod
35
+ def worst_outcome(cls, outcomes: Iterable['Outcome']) -> 'Outcome':
36
+ def _outcome_to_int(o: 'Outcome') -> int:
37
+ return cls._member_names_.index(o.name)
38
+
39
+ return max(outcomes, key=_outcome_to_int)
40
+
34
41
 
35
42
  class DigestHolder(BaseModel):
36
43
  value: Optional[str] = None
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes