rbx.cp 0.5.54__tar.gz → 0.5.56__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 (208) hide show
  1. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/PKG-INFO +7 -2
  2. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/pyproject.toml +7 -2
  3. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/checkers.py +11 -1
  4. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/cli.py +8 -0
  5. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/contest/schema.py +53 -4
  6. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/naming.py +20 -5
  7. rbx_cp-0.5.56/rbx/box/packaging/boca/upload.py +247 -0
  8. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/packaging/main.py +13 -1
  9. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/solutions.py +12 -1
  10. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/tasks.py +4 -2
  11. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/testcase_extractors.py +3 -0
  12. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/ui/captured_log.py +13 -8
  13. rbx_cp-0.5.56/rbx/box/ui/css/app.tcss +87 -0
  14. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/ui/main.py +5 -1
  15. rbx_cp-0.5.56/rbx/box/ui/screens/build.py +6 -0
  16. rbx_cp-0.5.56/rbx/box/ui/screens/command.py +35 -0
  17. {rbx_cp-0.5.54/rbx/box/ui → rbx_cp-0.5.56/rbx/box/ui/screens}/run.py +10 -38
  18. rbx_cp-0.5.56/rbx/box/ui/screens/run_explorer.py +5 -0
  19. rbx_cp-0.5.56/rbx/box/ui/screens/test_explorer.py +100 -0
  20. rbx_cp-0.5.56/rbx/box/ui/widgets/file_log.py +63 -0
  21. rbx_cp-0.5.56/rbx/box/ui/widgets/rich_log_box.py +5 -0
  22. rbx_cp-0.5.56/rbx/grading/judge/sandboxes/__init__.py +0 -0
  23. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/grading/judge/sandboxes/stupid_sandbox.py +5 -1
  24. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/grading/judge/sandboxes/timeit.py +2 -1
  25. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/boca/interactive/c +15 -8
  26. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/boca/interactive/cc +15 -8
  27. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/boca/interactive/cpp +15 -9
  28. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/boca/interactive/java +12 -5
  29. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/boca/interactive/kt +12 -5
  30. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/boca/interactive/py2 +15 -8
  31. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/boca/interactive/py3 +14 -9
  32. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/boca/run/c +2 -1
  33. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/boca/run/cc +2 -1
  34. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/boca/run/cpp +2 -1
  35. rbx_cp-0.5.54/rbx/box/ui/css/app.tcss +0 -48
  36. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/LICENSE +0 -0
  37. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/README.md +0 -0
  38. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/__init__.py +0 -0
  39. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/annotations.py +0 -0
  40. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/autoenum.py +0 -0
  41. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/__init__.py +0 -0
  42. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/builder.py +0 -0
  43. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/cd.py +0 -0
  44. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/code.py +0 -0
  45. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/compile.py +0 -0
  46. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/conftest.py +0 -0
  47. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/contest/__init__.py +0 -0
  48. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/contest/build_contest_statements.py +0 -0
  49. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/contest/contest_package.py +0 -0
  50. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/contest/contest_utils.py +0 -0
  51. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/contest/main.py +0 -0
  52. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/contest/statements.py +0 -0
  53. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/creation.py +0 -0
  54. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/deferred.py +0 -0
  55. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/download.py +0 -0
  56. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/dump_schemas.py +0 -0
  57. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/environment.py +0 -0
  58. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/extensions.py +0 -0
  59. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/formatting.py +0 -0
  60. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/generators.py +0 -0
  61. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/generators_test.py +0 -0
  62. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/header.py +0 -0
  63. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/lazy_importing_main.py +0 -0
  64. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/lazy_importing_test.py +0 -0
  65. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/main.py +0 -0
  66. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/package.py +0 -0
  67. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/packaging/boca/extension.py +0 -0
  68. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/packaging/boca/packager.py +0 -0
  69. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/packaging/contest_main.py +0 -0
  70. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/packaging/moj/packager.py +0 -0
  71. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/packaging/packager.py +0 -0
  72. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/packaging/polygon/packager.py +0 -0
  73. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/packaging/polygon/polygon_api.py +0 -0
  74. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/packaging/polygon/test.py +0 -0
  75. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/packaging/polygon/upload.py +0 -0
  76. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/packaging/polygon/xml_schema.py +0 -0
  77. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/presets/__init__.py +0 -0
  78. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/presets/fetch.py +0 -0
  79. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/presets/lock_schema.py +0 -0
  80. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/presets/schema.py +0 -0
  81. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/retries.py +0 -0
  82. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/sanitizers/warning_stack.py +0 -0
  83. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/schema.py +0 -0
  84. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/setter_config.py +0 -0
  85. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/solutions_test.py +0 -0
  86. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/state.py +0 -0
  87. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/statements/__init__.py +0 -0
  88. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/statements/build_statements.py +0 -0
  89. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/statements/builders.py +0 -0
  90. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/statements/joiners.py +0 -0
  91. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/statements/latex.py +0 -0
  92. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/statements/latex_jinja.py +0 -0
  93. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/statements/schema.py +0 -0
  94. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/stresses.py +0 -0
  95. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/stressing/__init__.py +0 -0
  96. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/stressing/finder_parser.py +0 -0
  97. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/stressing/generator_parser.py +0 -0
  98. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/testcase_utils.py +0 -0
  99. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/testcases/__init__.py +0 -0
  100. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/testcases/main.py +0 -0
  101. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/ui/__init__.py +0 -0
  102. {rbx_cp-0.5.54/rbx/grading → rbx_cp-0.5.56/rbx/box/ui/screens}/__init__.py +0 -0
  103. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/unit.py +0 -0
  104. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/validators.py +0 -0
  105. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/box/validators_test.py +0 -0
  106. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/checker.py +0 -0
  107. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/clone.py +0 -0
  108. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/config.py +0 -0
  109. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/conftest.py +0 -0
  110. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/console.py +0 -0
  111. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/create.py +0 -0
  112. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/edit.py +0 -0
  113. {rbx_cp-0.5.54/rbx/grading/judge → rbx_cp-0.5.56/rbx/grading}/__init__.py +0 -0
  114. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/grading/caching.py +0 -0
  115. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/grading/conftest.py +0 -0
  116. {rbx_cp-0.5.54/rbx/grading/judge/sandboxes → rbx_cp-0.5.56/rbx/grading/judge}/__init__.py +0 -0
  117. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/grading/judge/cacher.py +0 -0
  118. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/grading/judge/digester.py +0 -0
  119. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/grading/judge/sandbox.py +0 -0
  120. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/grading/judge/sandboxes/isolate.py +0 -0
  121. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/grading/judge/storage.py +0 -0
  122. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/grading/judge/test.py +0 -0
  123. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/grading/judge/testiso.py +0 -0
  124. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/grading/processing_context.py +0 -0
  125. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/grading/steps.py +0 -0
  126. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/grading/steps_with_caching.py +0 -0
  127. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/grading/steps_with_caching_run_test.py +0 -0
  128. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/grading_utils.py +0 -0
  129. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/hydration.py +0 -0
  130. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/main.py +0 -0
  131. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/metadata.py +0 -0
  132. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/providers/__init__.py +0 -0
  133. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/providers/codeforces.py +0 -0
  134. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/providers/provider.py +0 -0
  135. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/checkers/boilerplate.cpp +0 -0
  136. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/default_config.json +0 -0
  137. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/default_setter_config.mac.yml +0 -0
  138. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/default_setter_config.yml +0 -0
  139. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/envs/default.rbx.yml +0 -0
  140. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/envs/isolate.rbx.yml +0 -0
  141. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/boca/checker.sh +0 -0
  142. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/boca/compare.sh +0 -0
  143. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/boca/compile/c +0 -0
  144. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/boca/compile/cc +0 -0
  145. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/boca/compile/cpp +0 -0
  146. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/boca/compile/java +0 -0
  147. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/boca/compile/kt +0 -0
  148. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/boca/compile/pas +0 -0
  149. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/boca/compile/py2 +0 -0
  150. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/boca/compile/py3 +0 -0
  151. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/boca/interactor_compile.sh +0 -0
  152. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/boca/run/bkp +0 -0
  153. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/boca/run/java +0 -0
  154. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/boca/run/kt +0 -0
  155. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/boca/run/py2 +0 -0
  156. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/boca/run/py3 +0 -0
  157. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/moj/scripts/c/compile.sh +0 -0
  158. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/moj/scripts/c/prep.sh +0 -0
  159. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/moj/scripts/c/run.sh +0 -0
  160. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/moj/scripts/compare.sh +0 -0
  161. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/moj/scripts/cpp/compile.sh +0 -0
  162. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/moj/scripts/cpp/prep.sh +0 -0
  163. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/moj/scripts/cpp/run.sh +0 -0
  164. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/moj/scripts/interactor_prep.sh +0 -0
  165. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/moj/scripts/interactor_run.sh +0 -0
  166. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/moj/scripts/java/compile.sh +0 -0
  167. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/moj/scripts/java/prep.sh +0 -0
  168. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/moj/scripts/java/run.sh +0 -0
  169. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/moj/scripts/py2/compile.sh +0 -0
  170. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/moj/scripts/py2/prep.sh +0 -0
  171. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/moj/scripts/py2/run.sh +0 -0
  172. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/moj/scripts/py3/compile.sh +0 -0
  173. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/moj/scripts/py3/prep.sh +0 -0
  174. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/packagers/moj/scripts/py3/run.sh +0 -0
  175. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/presets/default/contest/contest.rbx.yml +0 -0
  176. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/presets/default/contest/statement/contest.rbx.tex +0 -0
  177. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/presets/default/contest/statement/olymp.sty +0 -0
  178. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/presets/default/contest/statement/template.rbx.tex +0 -0
  179. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/presets/default/preset.rbx.yml +0 -0
  180. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/presets/default/problem/.gitignore +0 -0
  181. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/presets/default/problem/gen.cpp +0 -0
  182. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/presets/default/problem/problem.rbx.yml +0 -0
  183. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/presets/default/problem/random.py +0 -0
  184. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/presets/default/problem/random.txt +0 -0
  185. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/presets/default/problem/sols/main.cpp +0 -0
  186. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/presets/default/problem/sols/slow.cpp +0 -0
  187. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/presets/default/problem/sols/wa.cpp +0 -0
  188. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/presets/default/problem/statement/olymp.sty +0 -0
  189. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/presets/default/problem/statement/projecao.png +0 -0
  190. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/presets/default/problem/statement/statement.rbx.tex +0 -0
  191. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/presets/default/problem/statement/template.rbx.tex +0 -0
  192. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/presets/default/problem/tests/samples/000.in +0 -0
  193. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/presets/default/problem/tests/samples/001.in +0 -0
  194. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/presets/default/problem/validator.cpp +0 -0
  195. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/presets/default/problem/wcmp.cpp +0 -0
  196. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/templates/rbx.h +0 -0
  197. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/resources/templates/template.cpp +0 -0
  198. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/run.py +0 -0
  199. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/schema.py +0 -0
  200. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/submit.py +0 -0
  201. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/submitors/__init__.py +0 -0
  202. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/submitors/codeforces.py +0 -0
  203. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/submitors/submitor.py +0 -0
  204. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/test.py +0 -0
  205. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/testcase.py +0 -0
  206. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/testcase_rendering.py +0 -0
  207. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/testing_utils.py +0 -0
  208. {rbx_cp-0.5.54 → rbx_cp-0.5.56}/rbx/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: rbx.cp
3
- Version: 0.5.54
3
+ Version: 0.5.56
4
4
  Summary:
5
5
  Author: Roberto Sales
6
6
  Requires-Python: >=3.9,<4.0
@@ -10,8 +10,12 @@ Classifier: Programming Language :: Python :: 3.10
10
10
  Classifier: Programming Language :: Python :: 3.11
11
11
  Classifier: Programming Language :: Python :: 3.12
12
12
  Classifier: Programming Language :: Python :: 3.13
13
+ Requires-Dist: aiofiles (>=24.1.0,<25.0.0)
13
14
  Requires-Dist: async-lru (>=2.0.5,<3.0.0)
15
+ Requires-Dist: beautifulsoup4 (>=4.13.4,<5.0.0)
14
16
  Requires-Dist: chardet (>=5.2.0,<6.0.0)
17
+ Requires-Dist: colour (>=0.1.5,<0.2.0)
18
+ Requires-Dist: dateparser (>=1.2.1,<2.0.0)
15
19
  Requires-Dist: fastapi (>=0.115.8,<0.116.0)
16
20
  Requires-Dist: filelock (>=3.14.0,<4.0.0)
17
21
  Requires-Dist: gitpython (>=3.1.43,<4.0.0)
@@ -32,7 +36,8 @@ Requires-Dist: requests (>=2.32.3,<3.0.0)
32
36
  Requires-Dist: rich (>=13.9.4,<14.0.0)
33
37
  Requires-Dist: ruyaml (>=0.91.0,<0.92.0)
34
38
  Requires-Dist: syncer (>=2.0.3,<3.0.0)
35
- Requires-Dist: textual (>=0.79.1,<0.80.0)
39
+ Requires-Dist: textual (>=3.1.1,<4.0.0)
40
+ Requires-Dist: textual-serve (>=1.1.2,<2.0.0)
36
41
  Requires-Dist: typer (>=0.15.1,<0.16.0)
37
42
  Description-Content-Type: text/markdown
38
43
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "rbx.cp"
3
- version = "0.5.54"
3
+ version = "0.5.56"
4
4
  description = ""
5
5
  packages = [
6
6
  {include = "rbx"}
@@ -27,7 +27,7 @@ pydantic-xml = {extras = ["lxml"], version = "^2.11.0"}
27
27
  python-iso639 = "^2024.4.27"
28
28
  more-itertools = "^10.5.0"
29
29
  gitpython = "^3.1.43"
30
- textual = "^0.79.1"
30
+ textual = "^3.1.1"
31
31
  pyte = "^0.8.2"
32
32
  questionary = "^2.1.0"
33
33
  lark = "^1.2.2"
@@ -37,6 +37,11 @@ syncer = "^2.0.3"
37
37
  async-lru = "^2.0.5"
38
38
  nest-asyncio = "^1.6.0"
39
39
  psutil = "^7.0.0"
40
+ textual-serve = "^1.1.2"
41
+ aiofiles = "^24.1.0"
42
+ colour = "^0.1.5"
43
+ beautifulsoup4 = "^4.13.4"
44
+ dateparser = "^1.2.1"
40
45
 
41
46
  [tool.poetry.scripts]
42
47
  rbc = "rbx.main:app"
@@ -1,6 +1,6 @@
1
1
  import pathlib
2
2
  import signal
3
- from typing import Optional
3
+ from typing import List, Optional
4
4
 
5
5
  import typer
6
6
 
@@ -53,6 +53,12 @@ def compile_interactor(progress: Optional[StatusProgress] = None) -> str:
53
53
  return digest
54
54
 
55
55
 
56
+ def _any_failed(logs: List[Optional[RunLog]]) -> bool:
57
+ return any(
58
+ log is None or log.exitstatus == SandboxBase.EXIT_SANDBOX_ERROR for log in logs
59
+ )
60
+
61
+
56
62
  def _check_pre_output(run_log: Optional[RunLog]) -> CheckerResult:
57
63
  pkg = package.find_problem_package_or_die()
58
64
 
@@ -283,6 +289,10 @@ async def check_communication(
283
289
  # No relevant error was found.
284
290
  return None
285
291
 
292
+ # 0. If any of the sandboxes failed, we should return an error.
293
+ if _any_failed([run_log, interactor_run_log]):
294
+ return CheckerResult(outcome=Outcome.INTERNAL_ERROR)
295
+
286
296
  # 1. If the solution received SIGPIPE or was terminated, it means the
287
297
  # interactor exited before it. Thus, check the interactor, as it might have
288
298
  # returned a checker verdict.
@@ -130,6 +130,14 @@ def ui():
130
130
  ui_pkg.start()
131
131
 
132
132
 
133
+ @app.command('serve', hidden=True)
134
+ def serve():
135
+ from textual_serve.server import Server
136
+
137
+ server = Server('rbx ui', port=8081)
138
+ server.serve()
139
+
140
+
133
141
  @app.command(
134
142
  'edit, e',
135
143
  rich_help_panel='Configuration',
@@ -1,7 +1,7 @@
1
1
  import pathlib
2
2
  from typing import Dict, List, Optional
3
3
 
4
- from pydantic import BaseModel, ConfigDict, Field
4
+ from pydantic import BaseModel, ConfigDict, Field, model_validator
5
5
 
6
6
  from rbx.box.schema import NameField, Primitive, expand_var
7
7
  from rbx.box.statements.schema import (
@@ -121,11 +121,60 @@ If not specified, will expect the problem to be in ./{short_name}/ folder.""",
121
121
 
122
122
  color: Optional[str] = Field(
123
123
  default=None,
124
- description="""Hex-based color that represents this problem in the contest.""",
125
- pattern=r'^[A-Za-z0-9]+$',
126
- max_length=6,
124
+ description="""
125
+ Color that represents this problem in the contest.
126
+
127
+ Can be a hex color (#abcdef or #abc format), or a color name among available X11 colors.
128
+
129
+ See https://en.wikipedia.org/wiki/X11_color_names for the list of supported color names.
130
+ """,
127
131
  )
128
132
 
133
+ colorName: Optional[str] = Field(
134
+ default=None,
135
+ description="""
136
+ A custom color name for the color provided by this problem.
137
+
138
+ If not provided, will try to infer a color name from the color provided.
139
+ """,
140
+ pattern=r'^[a-zA-Z]+$',
141
+ )
142
+
143
+ @model_validator(mode='after')
144
+ def check_color(self):
145
+ from colour import Color
146
+
147
+ if self.color is None:
148
+ return self
149
+
150
+ Color(self.color)
151
+ return self
152
+
153
+ @property
154
+ def hex_color(self) -> Optional[str]:
155
+ from colour import Color
156
+
157
+ if self.color is None:
158
+ return None
159
+
160
+ return Color(self.color).hex_l
161
+
162
+ @property
163
+ def color_name(self) -> Optional[str]:
164
+ if self.colorName is not None:
165
+ return self.colorName
166
+
167
+ if self.color is None:
168
+ return None
169
+
170
+ from colour import Color
171
+
172
+ color = Color(self.color)
173
+ web_color = color.web
174
+ if web_color.startswith('#'):
175
+ return 'unknown'
176
+ return web_color
177
+
129
178
  def get_path(self) -> pathlib.Path:
130
179
  return self.path or pathlib.Path(self.short_name)
131
180
 
@@ -1,27 +1,42 @@
1
- from typing import Optional
1
+ from typing import Optional, Tuple
2
2
 
3
3
  from rbx.box import package
4
4
  from rbx.box.contest import contest_package
5
+ from rbx.box.contest.schema import ContestProblem
5
6
 
6
7
 
7
- def get_problem_shortname() -> Optional[str]:
8
+ def get_problem_entry_in_contest() -> Optional[Tuple[int, ContestProblem]]:
8
9
  contest = contest_package.find_contest_package()
9
10
  if contest is None:
10
11
  return None
11
12
  problem_path = package.find_problem()
12
13
  contest_path = contest_package.find_contest()
13
14
 
14
- for problem in contest.problems:
15
+ for i, problem in enumerate(contest.problems):
15
16
  if problem.path is None:
16
17
  continue
17
18
  if (problem_path / 'problem.rbx.yml').samefile(
18
19
  contest_path / problem.path / 'problem.rbx.yml'
19
20
  ):
20
- return problem.short_name
21
-
21
+ return i, problem
22
22
  return None
23
23
 
24
24
 
25
+ def get_problem_shortname() -> Optional[str]:
26
+ entry = get_problem_entry_in_contest()
27
+ if entry is None:
28
+ return None
29
+ _, problem = entry
30
+ return problem.short_name
31
+
32
+
33
+ def get_problem_index() -> Optional[int]:
34
+ entry = get_problem_entry_in_contest()
35
+ if entry is None:
36
+ return None
37
+ return entry[0]
38
+
39
+
25
40
  def get_problem_name_with_contest_info() -> str:
26
41
  problem = package.find_problem_package_or_die()
27
42
  contest = contest_package.find_contest_package()
@@ -0,0 +1,247 @@
1
+ import datetime
2
+ import hashlib
3
+ import os
4
+ import pathlib
5
+ import re
6
+ import typing
7
+ from typing import Any, NoReturn, Optional, Tuple
8
+
9
+ import dateparser
10
+ import mechanize
11
+ import typer
12
+ from bs4 import BeautifulSoup
13
+
14
+ from rbx import console
15
+ from rbx.box import naming
16
+
17
+ ALERT_REGEX = re.compile(r'\<script[^\>]*\>\s*alert\(\'([^\']+)\'\);?\s*\<\/script\>')
18
+ UPLOAD_LOG_REGEX = re.compile(r'Problem (\d+) \([^\)]+\) updated')
19
+
20
+
21
+ def _parse_env_var(var: str, override: Optional[str]) -> str:
22
+ if override is not None:
23
+ return override
24
+ value = os.environ.get(var)
25
+ if value is None:
26
+ console.console.print(
27
+ f'[error][item]{var}[/item] is not set. Set it as an environment variable.[/error]'
28
+ )
29
+ raise typer.Exit(1)
30
+ return value
31
+
32
+
33
+ class BocaUploader:
34
+ def __init__(
35
+ self,
36
+ base_url: Optional[str] = None,
37
+ username: Optional[str] = None,
38
+ password: Optional[str] = None,
39
+ ):
40
+ self.base_url = _parse_env_var('BOCA_BASE_URL', base_url)
41
+ self.username = _parse_env_var('BOCA_USERNAME', username)
42
+ self.password = _parse_env_var('BOCA_PASSWORD', password)
43
+
44
+ self.br = mechanize.Browser()
45
+ self.br.set_handle_robots(False)
46
+ self.br.addheaders = [ # type: ignore
47
+ (
48
+ 'User-agent',
49
+ 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008071615 Fedora/3.0.1-1.fc9 Firefox/3.0.1',
50
+ )
51
+ ]
52
+
53
+ def error(self, message: str) -> NoReturn:
54
+ console.console.print(
55
+ f'[error]{message} (at [item]{self.base_url}[/item])[/error]',
56
+ )
57
+ raise typer.Exit(1)
58
+
59
+ def raw_error(self, message: str) -> NoReturn:
60
+ console.console.print(f'[error]{message}[/error]')
61
+ raise typer.Exit(1)
62
+
63
+ def log_response_alert(self, response: Any, message: str) -> Tuple[Any, str]:
64
+ if response is None:
65
+ self.raw_error(
66
+ f'{message} ([item]{self.base_url}[/item]):\nNo response received.'
67
+ )
68
+ html = response.read().decode()
69
+ alert = ALERT_REGEX.search(html)
70
+ if alert:
71
+ self.raw_error(
72
+ f'{message} ([item]{self.base_url}[/item]):\n{alert.group(1)}'
73
+ )
74
+ return response, html
75
+
76
+ def check_logs_for_update(self, problem_id: int) -> bool:
77
+ _, html = self.open(
78
+ f'{self.base_url}/admin/log.php',
79
+ error_msg='Error while checking whether package upload was successful',
80
+ )
81
+
82
+ soup = BeautifulSoup(html, 'html.parser')
83
+ table = soup.select_one('table:nth-of-type(3)')
84
+ if table is None:
85
+ self.raw_error(
86
+ 'Error while checking whether package upload was successful:\nNo logs table found.'
87
+ )
88
+ rows = table.select('tr:not(:first-child)')
89
+ for row in rows:
90
+ # Check whether the log line is recent.
91
+ date_cell = row.select('td:nth-of-type(5)')
92
+ if date_cell is None:
93
+ continue
94
+ date = date_cell[0].text.strip()
95
+
96
+ parsed_date = dateparser.parse(date)
97
+ if parsed_date is None:
98
+ console.console.print(
99
+ f'Error while checking whether package upload was successful:\nCould not parse date [item]{date}[/item].'
100
+ )
101
+ raise typer.Exit(1)
102
+ if parsed_date < datetime.datetime.now(
103
+ datetime.timezone.utc
104
+ ) - datetime.timedelta(minutes=1):
105
+ continue
106
+
107
+ # Check if the log line contains the problem id.
108
+ log_cell = row.select('td:nth-of-type(6)')
109
+ if log_cell is None:
110
+ continue
111
+ log_line = log_cell[0].text.strip()
112
+ match = UPLOAD_LOG_REGEX.match(log_line)
113
+ if match is None:
114
+ continue
115
+ found_id = int(match.group(1))
116
+ if found_id == problem_id:
117
+ return True
118
+ return False
119
+
120
+ def check_submit(self, response: Any, problem_id: int) -> bool:
121
+ if response is None:
122
+ self.raw_error(
123
+ 'Error while submitting problem to BOCA website:\nNo response received.'
124
+ )
125
+ html = response.read().decode()
126
+ alert = ALERT_REGEX.search(html)
127
+ if alert:
128
+ msg = alert.group(1)
129
+ if 'Violation' in msg:
130
+ return False
131
+ self.raw_error(
132
+ f'Error while submitting problem to BOCA website:\n{alert.group(1)}'
133
+ )
134
+ return self.check_logs_for_update(problem_id)
135
+
136
+ def open(self, url: str, error_msg: Optional[str] = None):
137
+ if error_msg is None:
138
+ error_msg = f'Error while opening [item]{url}[/item]'
139
+ response = self.br.open(url)
140
+ return self.log_response_alert(response, error_msg)
141
+
142
+ def login(self):
143
+ _, html = self.open(
144
+ f'{self.base_url}', error_msg='Error while opening BOCA login page'
145
+ )
146
+
147
+ needle = "js_myhash(document.form1.password.value)+'"
148
+ start = html.index(needle)
149
+ start_salt = start + len(needle)
150
+ end_salt = html.index("'", start_salt)
151
+ salt = html[start_salt:end_salt]
152
+ console.console.print(f'Using salt [item]{salt}[/item]')
153
+
154
+ pwd_hash = hashlib.sha256(self.password.encode()).hexdigest()
155
+ pwd_hash = hashlib.sha256((pwd_hash + salt).encode()).hexdigest()
156
+
157
+ login_url = f'{self.base_url}?name={self.username}&password={pwd_hash}'
158
+ self.open(login_url, error_msg='Error while logging in to BOCA')
159
+
160
+ def upload(self, file: pathlib.Path) -> bool:
161
+ self.open(
162
+ f'{self.base_url}/admin/problem.php',
163
+ error_msg='Error while opening BOCA problem upload page',
164
+ )
165
+ try:
166
+ form = self.br.select_form(name='form1')
167
+ except mechanize.FormNotFoundError:
168
+ self.error(
169
+ 'Problem upload form not found in BOCA website. This might happen when the login failed.'
170
+ )
171
+
172
+ form = typing.cast(mechanize.HTMLForm, self.br.form)
173
+ form.set_all_readonly(False)
174
+
175
+ problem_index = naming.get_problem_index()
176
+ if problem_index is None:
177
+ console.console.print(
178
+ 'It seems this problem is not part of a contest. Cannot upload it to BOCA.'
179
+ )
180
+ raise typer.Exit(1)
181
+
182
+ problem_shortname = naming.get_problem_shortname()
183
+ assert problem_shortname is not None
184
+ entry = naming.get_problem_entry_in_contest()
185
+ assert entry is not None
186
+ _, problem_entry = entry
187
+
188
+ hex_color = problem_entry.hex_color
189
+ if hex_color is None:
190
+ form['colorname'] = 'black'
191
+ form['color'] = '000000'
192
+ else:
193
+ assert problem_entry.color_name is not None
194
+ form['colorname'] = problem_entry.color_name.lower()
195
+ form['color'] = hex_color[1:]
196
+
197
+ form['problemnumber'] = f'{problem_index + 1}'
198
+ form['problemname'] = problem_shortname
199
+ form['confirmation'] = 'confirm'
200
+ form['autojudge_new_sel'] = ['all']
201
+ form['Submit3'] = 'Send'
202
+
203
+ with file.open('rb') as f:
204
+ form.add_file(
205
+ f,
206
+ filename=file.name,
207
+ name='probleminput',
208
+ content_type='application/zip',
209
+ )
210
+ response = self.br.submit()
211
+
212
+ return self.check_submit(response, problem_index + 1)
213
+
214
+ def login_and_upload(self, file: pathlib.Path):
215
+ RETRIES = 3
216
+
217
+ tries = 0
218
+ ok = False
219
+ while tries < RETRIES:
220
+ tries += 1
221
+
222
+ console.console.print('Logging in to BOCA...')
223
+ self.login()
224
+ console.console.print('Uploading problem to BOCA...')
225
+ if not self.upload(file):
226
+ console.console.print(
227
+ f'[warning]Potentially transient error while uploading problem to BOCA. Retrying ({tries}/{RETRIES})...[/warning]'
228
+ )
229
+ continue
230
+
231
+ ok = True
232
+ console.console.print(
233
+ '[success]Problem sent to BOCA. [item]rbx[/item] cannot determine the upload succeeded, check the website to be sure.[/success]'
234
+ )
235
+ break
236
+
237
+ if not ok:
238
+ console.console.print(
239
+ '[error]Persistent error while uploading problem to BOCA website.[/error]'
240
+ )
241
+ console.console.print(
242
+ '[warning]This might be caused by PHP max upload size limit (which usually defaults to 2MBF).[/warning]'
243
+ )
244
+ console.console.print(
245
+ '[warning]Check [item]https://www.php.net/manual/en/ini.core.php#ini.sect.file-uploads[/item] for more information.[/warning]'
246
+ )
247
+ raise typer.Exit(1)
@@ -93,10 +93,22 @@ async def polygon(
93
93
  @syncer.sync
94
94
  async def boca(
95
95
  verification: environment.VerificationParam,
96
+ upload: bool = typer.Option(
97
+ False,
98
+ '--upload',
99
+ '-u',
100
+ help='If set, will upload the package to BOCA.',
101
+ ),
96
102
  ):
97
103
  from rbx.box.packaging.boca.packager import BocaPackager
98
104
 
99
- await run_packager(BocaPackager, verification=verification)
105
+ result_path = await run_packager(BocaPackager, verification=verification)
106
+
107
+ if upload:
108
+ from rbx.box.packaging.boca.upload import BocaUploader
109
+
110
+ uploader = BocaUploader('http://137.184.1.39/boca', 'admin', 'boca')
111
+ uploader.login_and_upload(result_path)
100
112
 
101
113
 
102
114
  @app.command('moj', help='Build a package for MOJ.')
@@ -77,6 +77,7 @@ class GroupSkeleton(BaseModel):
77
77
 
78
78
  class SolutionReportSkeleton(BaseModel):
79
79
  solutions: List[Solution]
80
+ entries: List[TestcaseEntry]
80
81
  groups: List[GroupSkeleton]
81
82
  limits: Dict[str, Limits]
82
83
 
@@ -245,10 +246,16 @@ def _get_report_skeleton(
245
246
  for group in pkg.testcases:
246
247
  testcases = find_built_testcases(group)
247
248
  groups.append(GroupSkeleton(name=group.name, testcases=testcases))
249
+ entries = [
250
+ TestcaseEntry(group=group.name, index=i)
251
+ for group in groups
252
+ for i in range(len(group.testcases))
253
+ ]
248
254
  return SolutionReportSkeleton(
249
255
  solutions=solutions,
250
256
  groups=groups,
251
257
  limits=limits,
258
+ entries=entries,
252
259
  )
253
260
 
254
261
 
@@ -349,7 +356,7 @@ def run_solutions(
349
356
  timelimit_override: Optional[int] = None,
350
357
  sanitized: bool = False,
351
358
  ) -> RunSolutionResult:
352
- return RunSolutionResult(
359
+ result = RunSolutionResult(
353
360
  skeleton=_get_report_skeleton(
354
361
  tracked_solutions,
355
362
  verification=verification,
@@ -364,6 +371,10 @@ def run_solutions(
364
371
  sanitized=sanitized,
365
372
  ),
366
373
  )
374
+ skeleton_file = package.get_problem_runs_dir() / 'skeleton.yml'
375
+ skeleton_file.parent.mkdir(parents=True, exist_ok=True)
376
+ skeleton_file.write_text(utils.model_to_yaml(result.skeleton))
377
+ return result
367
378
 
368
379
 
369
380
  async def _generate_testcase_interactively(
@@ -272,11 +272,13 @@ async def _run_communication_solution_on_testcase(
272
272
 
273
273
  log_path.write_text(model_to_yaml(eval))
274
274
 
275
+ interactor_log_path = output_path.with_suffix('.int.log')
276
+ interactor_log_path.unlink(missing_ok=True)
275
277
  if interactor_run_log is not None:
276
- interactor_log_path = output_path.with_suffix('.int.log')
277
278
  interactor_log_path.write_text(model_to_yaml(interactor_run_log))
279
+ solution_log_path = output_path.with_suffix('.sol.log')
280
+ solution_log_path.unlink(missing_ok=True)
278
281
  if run_log is not None:
279
- solution_log_path = output_path.with_suffix('.sol.log')
280
282
  solution_log_path.write_text(model_to_yaml(run_log))
281
283
  return eval
282
284
 
@@ -96,6 +96,9 @@ class GeneratorScriptEntry(BaseModel):
96
96
  path: pathlib.Path
97
97
  line: int
98
98
 
99
+ def __str__(self) -> str:
100
+ return f'{self.path}:{self.line}'
101
+
99
102
 
100
103
  class GenerationMetadata(BaseModel):
101
104
  copied_to: Testcase
@@ -18,8 +18,6 @@ from rich.segment import Segment
18
18
  from rich.style import Style
19
19
  from rich.text import Text
20
20
  from textual import events
21
- from textual.app import DEFAULT_COLORS
22
- from textual.design import ColorSystem
23
21
  from textual.geometry import Size
24
22
  from textual.scroll_view import ScrollView
25
23
  from textual.strip import Strip
@@ -95,6 +93,13 @@ class LogDisplay(ScrollView, can_focus=True):
95
93
  self.send_queue = asyncio.Queue()
96
94
  self.exitcode = None
97
95
 
96
+ def _resize(self):
97
+ self.virtual_size = Size(
98
+ width=self.size.width - 2, # Account for scroll bar.
99
+ height=self.virtual_size.height,
100
+ )
101
+ self._screen.resize(self._max_lines, self.virtual_size.width)
102
+
98
103
  async def on_resize(self, _event: events.Resize):
99
104
  if self.emulator is None:
100
105
  return
@@ -161,6 +166,9 @@ class LogDisplay(ScrollView, can_focus=True):
161
166
  cb()
162
167
  self.recv_task.cancel()
163
168
 
169
+ def on_unmount(self):
170
+ self.disconnect()
171
+
164
172
  async def recv(self):
165
173
  while True:
166
174
  msg = await self.recv_queue.get()
@@ -270,12 +278,7 @@ class LogDisplay(ScrollView, can_focus=True):
270
278
  def detect_textual_colors(self) -> dict:
271
279
  """Returns the currently used colors of textual depending on dark-mode."""
272
280
 
273
- if self.app.dark:
274
- color_system: ColorSystem = DEFAULT_COLORS['dark']
275
- else:
276
- color_system: ColorSystem = DEFAULT_COLORS['light']
277
-
278
- return color_system.generate()
281
+ return self.app.current_theme.to_color_system().generate()
279
282
 
280
283
  def render_line(self, y: int) -> Strip:
281
284
  scroll_x, scroll_y = self.scroll_offset
@@ -312,6 +315,8 @@ class LogDisplay(ScrollView, can_focus=True):
312
315
  pout = os.fdopen(fd, 'w+b', 0)
313
316
  data: Optional[str] = None
314
317
 
318
+ self._resize()
319
+
315
320
  def on_output():
316
321
  nonlocal data
317
322
  try: