rbx.cp 0.5.64__py3-none-any.whl → 0.5.66__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
rbx/box/cli.py CHANGED
@@ -3,7 +3,7 @@ import shlex
3
3
  import shutil
4
4
  import sys
5
5
  import tempfile
6
- from typing import Annotated, Optional
6
+ from typing import Annotated, List, Optional
7
7
 
8
8
  import rich
9
9
  import rich.prompt
@@ -136,6 +136,13 @@ def ui():
136
136
  ui_pkg.start()
137
137
 
138
138
 
139
+ @app.command('diff', hidden=True)
140
+ def diff(path1: pathlib.Path, path2: pathlib.Path):
141
+ from rbx.box.ui import main as ui_pkg
142
+
143
+ ui_pkg.start_differ(path1, path2)
144
+
145
+
139
146
  @app.command('serve', hidden=True)
140
147
  def serve():
141
148
  from textual_serve.server import Server
@@ -178,10 +185,10 @@ async def build(verification: environment.VerificationParam):
178
185
  @syncer.sync
179
186
  async def run(
180
187
  verification: environment.VerificationParam,
181
- solution: Annotated[
182
- Optional[str],
188
+ solutions: Annotated[
189
+ Optional[List[str]],
183
190
  typer.Argument(
184
- help='Path to solution to run. If not specified, will run all solutions.'
191
+ help='Path to solutions to run. If not specified, will run all solutions.'
185
192
  ),
186
193
  ] = None,
187
194
  outcome: Optional[str] = typer.Option(
@@ -235,11 +242,16 @@ async def run(
235
242
  str(solution.path)
236
243
  for solution in get_matching_solutions(ExpectedOutcome(outcome))
237
244
  }
238
- if solution:
239
- tracked_solutions = {solution}
245
+ if solutions:
246
+ tracked_solutions = set(solutions)
240
247
 
241
248
  if choice:
242
- tracked_solutions = set(await pick_solutions(tracked_solutions))
249
+ tracked_solutions = set(
250
+ await pick_solutions(
251
+ tracked_solutions,
252
+ extra_solutions=solutions,
253
+ )
254
+ )
243
255
  if not tracked_solutions:
244
256
  console.console.print('[error]No solutions selected. Exiting.[/error]')
245
257
  raise typer.Exit(1)
@@ -395,10 +407,10 @@ async def time(
395
407
  @syncer.sync
396
408
  async def irun(
397
409
  verification: environment.VerificationParam,
398
- solution: Annotated[
399
- Optional[str],
410
+ solutions: Annotated[
411
+ Optional[List[str]],
400
412
  typer.Argument(
401
- help='Path to solution to run. If not specified, will run all solutions.'
413
+ help='Path to solutions to run. If not specified, will run all solutions.'
402
414
  ),
403
415
  ] = None,
404
416
  outcome: Optional[str] = typer.Option(
@@ -466,11 +478,16 @@ async def irun(
466
478
  str(solution.path)
467
479
  for solution in get_matching_solutions(ExpectedOutcome(outcome))
468
480
  }
469
- if solution:
470
- tracked_solutions = {solution}
481
+ if solutions:
482
+ tracked_solutions = set(solutions)
471
483
 
472
484
  if choice:
473
- tracked_solutions = set(await pick_solutions(tracked_solutions))
485
+ tracked_solutions = set(
486
+ await pick_solutions(
487
+ tracked_solutions,
488
+ extra_solutions=solutions,
489
+ )
490
+ )
474
491
  if not tracked_solutions:
475
492
  console.console.print('[error]No solutions selected. Exiting.[/error]')
476
493
  raise typer.Exit(1)
rbx/box/formatting.py CHANGED
@@ -20,7 +20,11 @@ def href(url: os.PathLike[str], text: Optional[str] = None, style: str = 'item')
20
20
 
21
21
  if isinstance(url, pathlib.Path):
22
22
  url = url.resolve()
23
- return f'[{style}][link={url}]{text}[/link][/{style}]'
23
+
24
+ url_str = str(url)
25
+ if pathlib.Path(url_str).exists():
26
+ url_str = f'file://{url_str}'
27
+ return f'[{style}][link={url_str}]{text}[/link][/{style}]'
24
28
 
25
29
 
26
30
  def get_formatted_memory(memory_in_bytes: int, mib_decimal_places: int = 0) -> str:
rbx/box/package.py CHANGED
@@ -150,6 +150,19 @@ def get_problem_cache_dir(root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
150
150
  return cache_dir
151
151
 
152
152
 
153
+ @functools.cache
154
+ def get_problem_remote_dir(
155
+ platform: Optional[str] = None, root: pathlib.Path = pathlib.Path()
156
+ ) -> pathlib.Path:
157
+ remote_dir = get_problem_cache_dir(root) / '.remote'
158
+ remote_dir.mkdir(parents=True, exist_ok=True)
159
+
160
+ if platform is not None:
161
+ remote_dir = remote_dir / platform
162
+ remote_dir.mkdir(parents=True, exist_ok=True)
163
+ return remote_dir
164
+
165
+
153
166
  @functools.cache
154
167
  def get_problem_storage_dir(root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
155
168
  storage_dir = get_problem_cache_dir(root) / '.storage'
@@ -4,6 +4,7 @@ import hashlib
4
4
  import os
5
5
  import pathlib
6
6
  import re
7
+ import shutil
7
8
  import typing
8
9
  from typing import Any, NoReturn, Optional, Tuple
9
10
 
@@ -248,13 +249,50 @@ class BocaUploader:
248
249
  '[error]Persistent error while uploading problem to BOCA website.[/error]'
249
250
  )
250
251
  console.console.print(
251
- '[warning]This might be caused by PHP max upload size limit (which usually defaults to 2MBF).[/warning]'
252
+ '[warning]This might be caused by PHP max upload size limit (which usually defaults to 2MB).[/warning]'
252
253
  )
253
254
  console.console.print(
254
255
  '[warning]Check [item]https://www.php.net/manual/en/ini.core.php#ini.sect.file-uploads[/item] for more information.[/warning]'
255
256
  )
256
257
  raise typer.Exit(1)
257
258
 
259
+ def download_run(self, run_number: int, site_number: int, into_dir: pathlib.Path):
260
+ url = f'{self.base_url}/admin/runedit.php?runnumber={run_number}&runsitenumber={site_number}'
261
+ _, html = self.open(
262
+ url,
263
+ error_msg=f'Error while downloading BOCA run [item]{run_number}-{site_number}[/item]',
264
+ )
265
+
266
+ soup = BeautifulSoup(html, 'html.parser')
267
+ rows = soup.select('tr')
268
+
269
+ href: Optional[str] = None
270
+ filename: Optional[pathlib.Path] = None
271
+
272
+ for row in rows:
273
+ row_text = row.select('td')[0].text.strip().lower()
274
+ if row_text != "team's code:":
275
+ continue
276
+ link_col = row.select_one('td:nth-of-type(2) a:nth-of-type(1)')
277
+ if link_col is None:
278
+ continue
279
+ href = str(link_col.attrs['href'])
280
+ filename = pathlib.Path(link_col.text.strip())
281
+ break
282
+
283
+ if href is None or filename is None:
284
+ self.raw_error(
285
+ "Error while downloading run:\nNo link to team's code found."
286
+ )
287
+
288
+ link = self.br.find_link(url=href)
289
+ tmp_file, _ = self.br.retrieve(link.absolute_url)
290
+ if tmp_file is None:
291
+ self.raw_error('Error while downloading run:\nDownloaded file is None.')
292
+ final_path = into_dir / filename.with_stem(f'{run_number}-{site_number}')
293
+ shutil.move(tmp_file, final_path)
294
+ return final_path
295
+
258
296
 
259
297
  @functools.lru_cache
260
298
  def get_boca_uploader(
rbx/box/solutions.py CHANGED
@@ -159,12 +159,10 @@ def compile_solutions(
159
159
 
160
160
  compiled_solutions = {}
161
161
 
162
- for solution in pkg.solutions:
163
- if (
164
- tracked_solutions is not None
165
- and str(solution.path) not in tracked_solutions
166
- ):
167
- continue
162
+ if tracked_solutions is None:
163
+ tracked_solutions = set(str(sol.path) for sol in pkg.solutions)
164
+
165
+ for solution in expand_solutions(list(tracked_solutions)):
168
166
  if progress:
169
167
  progress.update(f'Compiling solution {href(solution.path)}...')
170
168
  try:
@@ -254,11 +252,7 @@ def _get_solutions_for_skeleton(
254
252
  if verification.value >= VerificationLevel.ALL_SOLUTIONS.value or is_fast(sol)
255
253
  ]
256
254
  if tracked_solutions is not None:
257
- solutions = [
258
- solution
259
- for solution in solutions
260
- if str(solution.path) in tracked_solutions
261
- ]
255
+ solutions = expand_solutions(list(tracked_solutions))
262
256
  return solutions
263
257
 
264
258
 
@@ -726,20 +720,105 @@ def _get_solution_repr(sol: Solution) -> List[Tuple[str, str]]:
726
720
  ]
727
721
 
728
722
 
729
- async def pick_solutions(tracked_solutions: Optional[Set[str]]) -> List[str]:
723
+ # TODO: refactor this function
724
+ def _expand_remote_sol(sol: str) -> Optional[str]:
725
+ from rbx.box.packaging.boca import upload as boca_upload
726
+
727
+ # Only BOCA supported for now.
728
+ assert sol.startswith('@')
729
+ sol = sol[1:]
730
+ if '-' in sol:
731
+ run_number, site_number = sol.split('-', 1)
732
+ run_number = int(run_number)
733
+ site_number = int(site_number)
734
+ else:
735
+ run_number = int(sol)
736
+ site_number = 1
737
+
738
+ expected_glob = (
739
+ package.get_problem_remote_dir('boca') / f'{run_number}-{site_number}.*'
740
+ )
741
+ expected_glob = expected_glob.relative_to(pathlib.Path.cwd())
742
+ for path in pathlib.Path.cwd().glob(str(expected_glob)):
743
+ if path.is_file():
744
+ relpath = path.resolve().relative_to(pathlib.Path.cwd())
745
+ console.console.print(f'Retrieving {href(relpath)} from cache...')
746
+ return str(relpath)
747
+
748
+ boca_uploader = boca_upload.get_boca_uploader()
749
+ boca_uploader.login()
750
+ sol_path = boca_uploader.download_run(
751
+ run_number, site_number, package.get_problem_remote_dir('boca')
752
+ )
753
+ console.console.print(f'Downloaded {href(sol_path)} from BOCA...')
754
+ return str(sol_path.resolve().relative_to(pathlib.Path.cwd()))
755
+
756
+
757
+ def _expand_remote_sols(sols: List[str]) -> List[str]:
758
+ res = []
759
+ for sol in sols:
760
+ if not sol.startswith('@'):
761
+ res.append(sol)
762
+ continue
763
+ expanded = _expand_remote_sol(sol)
764
+ if expanded is None:
765
+ continue
766
+ res.append(expanded)
767
+ return res
768
+
769
+
770
+ def expand_solutions(sols: List[str]) -> List[Solution]:
730
771
  pkg = package.find_problem_package_or_die()
731
- if tracked_solutions is None:
732
- tracked_solutions = set(str(sol.path) for sol in pkg.solutions)
772
+ seen_sols = {str(sol.path): sol for sol in pkg.solutions}
773
+
774
+ # Download remote sols.
775
+ sols = _expand_remote_sols(sols)
776
+
777
+ # Ensure sols exist.
778
+ sols = [sol for sol in sols if pathlib.Path(sol).is_file()]
779
+
780
+ res = []
781
+ for sol in sols:
782
+ if sol in seen_sols:
783
+ res.append(seen_sols[sol])
784
+ else:
785
+ res.append(
786
+ Solution(path=pathlib.Path(sol), outcome=ExpectedOutcome.ACCEPTED)
787
+ )
788
+ return res
789
+
733
790
 
791
+ async def pick_solutions(
792
+ tracked_solutions: Optional[Set[str]],
793
+ extra_solutions: Optional[List[str]] = None,
794
+ ) -> List[str]:
795
+ pkg = package.find_problem_package_or_die()
734
796
  # Store in a separate list to maintain order with the package declaration.
735
797
  import questionary
736
798
 
737
799
  choices = [
738
- questionary.Choice(title=_get_solution_repr(sol), value=str(sol.path))
800
+ questionary.Choice(
801
+ title=_get_solution_repr(sol),
802
+ value=str(sol.path),
803
+ checked=tracked_solutions is None or str(sol.path) in tracked_solutions,
804
+ )
739
805
  for sol in pkg.solutions
740
- if str(sol.path) in tracked_solutions
741
806
  ]
742
807
 
808
+ seen_sols = set(str(sol.path) for sol in pkg.solutions)
809
+
810
+ if extra_solutions is not None:
811
+ # Add only new solutions.
812
+ choices.extend(
813
+ questionary.Choice(
814
+ title=_get_solution_repr(sol),
815
+ value=str(sol.path),
816
+ checked=True,
817
+ )
818
+ for sol in expand_solutions(extra_solutions)
819
+ if str(sol.path) not in seen_sols
820
+ )
821
+
743
822
  picked = await questionary.checkbox('Select solutions', choices=choices).ask_async()
744
823
  if picked is None:
745
824
  raise typer.Abort()
rbx/box/testcase_utils.py CHANGED
@@ -172,6 +172,7 @@ def parse_interaction(file: pathlib.Path) -> TestcaseInteraction:
172
172
  )
173
173
  raise typer.Exit(1) from None
174
174
 
175
+ # Crop file.
175
176
  rest = f.read()
176
177
  start = 0
177
178
 
@@ -202,13 +203,17 @@ def parse_interaction(file: pathlib.Path) -> TestcaseInteraction:
202
203
  nxt_start, _ = nxt
203
204
  return (pipe, (prefix_end, nxt_start))
204
205
 
205
- while True:
206
+ # TODO: optimize
207
+ blocks = 0
208
+ MAX_BLOCKS = 1024
209
+ while blocks < MAX_BLOCKS:
206
210
  block = _find_next_block()
207
211
  if block is None:
208
212
  break
209
213
  pipe, (st, nd) = block
210
214
  entries.append(TestcaseInteractionEntry(data=rest[st:nd], pipe=pipe))
211
215
  start = nd
216
+ blocks += 1
212
217
 
213
218
  return TestcaseInteraction(
214
219
  prefixes=(interactor_prefix, solution_prefix),
rbx/box/ui/css/app.tcss CHANGED
@@ -21,6 +21,20 @@ OptionList {
21
21
  border: solid $accent;
22
22
  }
23
23
 
24
+ DiffBox {
25
+ border: solid $accent;
26
+ height: 1fr;
27
+ width: 1fr;
28
+ Markdown {
29
+ height: 1fr;
30
+ width: 1fr;
31
+
32
+ .code_inline {
33
+ background: $background;
34
+ }
35
+ }
36
+ }
37
+
24
38
  Button {
25
39
  width: 1fr;
26
40
  background: $accent;
rbx/box/ui/main.py CHANGED
@@ -1,3 +1,4 @@
1
+ import pathlib
1
2
  from typing import Type
2
3
 
3
4
  from textual.app import App, ComposeResult
@@ -5,6 +6,7 @@ from textual.containers import Center
5
6
  from textual.screen import Screen
6
7
  from textual.widgets import Footer, Header, OptionList
7
8
 
9
+ from rbx.box.ui.screens.differ import DifferScreen
8
10
  from rbx.box.ui.screens.run_explorer import RunExplorerScreen
9
11
  from rbx.box.ui.screens.test_explorer import TestExplorerScreen
10
12
 
@@ -35,6 +37,25 @@ class rbxApp(App):
35
37
  self.push_screen(screen_cls())
36
38
 
37
39
 
40
+ class rbxDifferApp(App):
41
+ TITLE = 'rbx differ'
42
+ CSS_PATH = 'css/app.tcss'
43
+ BINDINGS = [('q', 'quit', 'Quit')]
44
+
45
+ def __init__(self, path1: pathlib.Path, path2: pathlib.Path):
46
+ super().__init__()
47
+ self.path1 = path1
48
+ self.path2 = path2
49
+
50
+ def on_mount(self):
51
+ self.push_screen(DifferScreen(self.path1, self.path2))
52
+
53
+
38
54
  def start():
39
55
  app = rbxApp()
40
56
  app.run()
57
+
58
+
59
+ def start_differ(path1: pathlib.Path, path2: pathlib.Path):
60
+ app = rbxDifferApp(path1, path2)
61
+ app.run()
@@ -0,0 +1,29 @@
1
+ import pathlib
2
+
3
+ from textual.app import ComposeResult
4
+ from textual.containers import Vertical
5
+ from textual.screen import Screen
6
+ from textual.widgets import Footer, Header
7
+
8
+ from rbx.box.ui.widgets.diff_box import DiffBox
9
+
10
+
11
+ class DifferScreen(Screen):
12
+ BINDINGS = [
13
+ ('q', 'quit', 'Quit'),
14
+ ]
15
+
16
+ def __init__(self, path1: pathlib.Path, path2: pathlib.Path):
17
+ super().__init__()
18
+ self.path1 = path1
19
+ self.path2 = path2
20
+
21
+ def compose(self) -> ComposeResult:
22
+ yield Header()
23
+ yield Footer()
24
+ with Vertical():
25
+ yield DiffBox()
26
+
27
+ def on_mount(self):
28
+ diff = self.query_one(DiffBox)
29
+ diff.paths = (self.path1, self.path2)
@@ -0,0 +1,38 @@
1
+ import difflib
2
+ import pathlib
3
+ from typing import Optional, Tuple
4
+
5
+ from rich.markdown import Markdown
6
+ from textual.app import ComposeResult
7
+ from textual.reactive import reactive
8
+ from textual.widget import Widget
9
+ from textual.widgets import RichLog
10
+
11
+
12
+ def compute_diff(file1: pathlib.Path, file2: pathlib.Path) -> str:
13
+ lines1 = file1.read_text().splitlines(keepends=True)
14
+ lines2 = file2.read_text().splitlines(keepends=True)
15
+ return ''.join(difflib.ndiff(lines1, lines2))
16
+
17
+
18
+ class DiffBox(Widget, can_focus=False):
19
+ paths: reactive[Optional[Tuple[pathlib.Path, pathlib.Path]]] = reactive(None)
20
+
21
+ def __init__(self):
22
+ super().__init__()
23
+
24
+ def compose(self) -> ComposeResult:
25
+ md = RichLog()
26
+ md.border_title = 'Differ'
27
+ yield md
28
+
29
+ async def watch_paths(self, paths: Optional[Tuple[pathlib.Path, pathlib.Path]]):
30
+ log = self.query_one(RichLog)
31
+ log.clear()
32
+ if paths is None:
33
+ return
34
+ file1, file2 = paths
35
+ md = Markdown(
36
+ f'```diff\n{compute_diff(file1, file2)}\n```', code_theme='monokai'
37
+ )
38
+ log.write(md)
rbx/box/unit.py CHANGED
@@ -148,8 +148,6 @@ async def run_validator_unit_tests(progress: StatusProgress):
148
148
 
149
149
  async def run_checker_unit_tests(progress: StatusProgress):
150
150
  pkg = package.find_problem_package_or_die()
151
- if not pkg.unitTests.checker:
152
- return
153
151
 
154
152
  if not package.get_checker():
155
153
  console.console.print(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: rbx.cp
3
- Version: 0.5.64
3
+ Version: 0.5.66
4
4
  Summary:
5
5
  Author: Roberto Sales
6
6
  Requires-Python: >=3.9,<4.0
@@ -5,7 +5,7 @@ rbx/box/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  rbx/box/builder.py,sha256=MDm2qqmhedAbhn3rWP6cDwbBsGhV6sz_2sg1zLkPDw0,3613
6
6
  rbx/box/cd.py,sha256=KGaqXHnv2hCumZS2VdMezFfwFEQcSq0rt05_KZtuQAo,1570
7
7
  rbx/box/checkers.py,sha256=aGciafGNQ39UqndTEJRT3J8ngv_UigYxOT30yMzCwEI,12752
8
- rbx/box/cli.py,sha256=mockAftbBYRt587_Fjbkpk7GZtj0MxcRJGoHBQxfTXE,27139
8
+ rbx/box/cli.py,sha256=pURRvZSYS9fZCAheAFi6Dk1FHN4bw_xwT6n32U00WDQ,27532
9
9
  rbx/box/code.py,sha256=2oC1JbZiwfeg53ZICPig-KJYchqFRIZz-inlM-cLc7Q,19911
10
10
  rbx/box/compile.py,sha256=OJLthDQ921w9vyoE6Gk1Df54i5RwtRJ2YG-8XEfefcs,2489
11
11
  rbx/box/conftest.py,sha256=sEmciXSeDC-wmrZ1JSxbsUenKNP_VWW32mrCun2pY3I,1070
@@ -22,7 +22,7 @@ rbx/box/download.py,sha256=DxAiAk4lDYWEz1C9UTvZzHTq6hgm4fxGezApm2IkCTM,2601
22
22
  rbx/box/dump_schemas.py,sha256=3j5t47_vJmXj0BCczxDX6ByOcsfolGEDNCBXlPpk86w,593
23
23
  rbx/box/environment.py,sha256=Kp69MekUwwoVpupnafUcN5KAbP-ZTCwe0OQXt1h0FN8,11859
24
24
  rbx/box/extensions.py,sha256=Von8kIeXvNFTkGlMRMTvL2HIHPwlkuiMswr-ydbGV1w,519
25
- rbx/box/formatting.py,sha256=csscjgWrTa6zjs2ueVCFRzA3WxdUFbzkzOxHWyQVerw,1049
25
+ rbx/box/formatting.py,sha256=pPijbtoF2zK-8_y_Q7TOxMJAojZcMmFew4aCCSVfNRQ,1154
26
26
  rbx/box/generators.py,sha256=RE0-D91BB-3rNDQXvCFWzU9iMhKIc_ALp960oGfM-rY,13780
27
27
  rbx/box/generators_test.py,sha256=J7aBfuJhU84MWDWzgReRoOuQw_hVa09B8gTKAvL2XVo,1987
28
28
  rbx/box/git_utils.py,sha256=VlUgzuHOCnrjjiJQnDB32qDHbHw_zkwgA7wm4bloibc,750
@@ -31,10 +31,10 @@ rbx/box/lazy_importing_main.py,sha256=6Z8As7qVFFT619xHH9Xt8VCH57NjC4aDxfAgkWiUwT
31
31
  rbx/box/lazy_importing_test.py,sha256=B0-b3y_DkxEmtVfu4NfmVsgVdFl6kRCsEL6GLMHJISo,628
32
32
  rbx/box/main.py,sha256=a8CYi77kOywPFly4-ucEIJLXQW-1NFp91kK2fA42YTE,86
33
33
  rbx/box/naming.py,sha256=pOG37X_wQM9CCSYwJIUf-b-ZHEs_nchO7wQEdP_quJg,1367
34
- rbx/box/package.py,sha256=pRsA5UsKNnD2uJcm8x01uQHUbCxoLbJvhoeqf3nyrew,14290
34
+ rbx/box/package.py,sha256=0di0idKhnyLI3I5MR9lAyXgX-hu5vOW23VyfKpOg-9E,14684
35
35
  rbx/box/packaging/boca/extension.py,sha256=EQALNEOv4zVDXSKs_dk11n92y7cBZVn8TogIK683lE0,890
36
36
  rbx/box/packaging/boca/packager.py,sha256=kiCcP9roRCiDU0Xr5PDKO83W1yN6cyg-EKMF9fxE0EY,11950
37
- rbx/box/packaging/boca/upload.py,sha256=y3DNMLNZyAwMdfBLUZRzxnr8cHwLegpH_NtgKW2Zwvw,9260
37
+ rbx/box/packaging/boca/upload.py,sha256=ItCqL9Ae2afwvU8mPoo95XXddTFMH3dlsMBnQ4cq3hw,10734
38
38
  rbx/box/packaging/contest_main.py,sha256=UsRfIdNmOf0iLUbzgjxzyECfMuCQINstG1SCClGHaUQ,2808
39
39
  rbx/box/packaging/main.py,sha256=kLFcMz0xOTZgtJh6P0Rr-zfq0NCbvx7HiQQ_0YmsvGg,3826
40
40
  rbx/box/packaging/moj/packager.py,sha256=FjghOe5CPlaF1GqK0NZgWVV_eYWpdTmz88bh04yeAyI,8708
@@ -52,7 +52,7 @@ rbx/box/retries.py,sha256=cZcNYLVHFDbhbeqMzxITgo8SYY8qzyxm0tIYcmWl1Ek,4877
52
52
  rbx/box/sanitizers/warning_stack.py,sha256=RI97_GJgdjTKIXY_r0EKp5h0qQQSDSdNDh5K7zINrqs,2861
53
53
  rbx/box/schema.py,sha256=y736-wZdGw56T6eDC_m7NAm2XRUdauBXJRQkQO79fpc,16264
54
54
  rbx/box/setter_config.py,sha256=9iObg6BwxQhFAhIOk31Jc0BDDpRYVGf3SyLIOsWIltM,4393
55
- rbx/box/solutions.py,sha256=pQG_4HVpFds6C0gcxmvKr557s7IhYmLoYB_s_1GgtAI,49049
55
+ rbx/box/solutions.py,sha256=peTeu4JrXUFy5yX1V9x-YCG8d8XOVxlMUI-FriQtYlc,51519
56
56
  rbx/box/solutions_test.py,sha256=PX1TQoRzNd9mi1SGsG7WFrpqFgNrNX5Kwt0mkwFdoOA,1749
57
57
  rbx/box/state.py,sha256=MMf3DvfQji0jKEliCHct2Tpp_0epL1tvP8HbHNArQIc,166
58
58
  rbx/box/statements/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -68,16 +68,17 @@ rbx/box/stressing/finder_parser.py,sha256=jXpYNa4FyugzmHi3r96Uv4rU1krRQJc5Ihr9jf
68
68
  rbx/box/stressing/generator_parser.py,sha256=oHZryjR3YohgaSO9WEirQ7b2e-98WgZStF0N99W4Thw,7380
69
69
  rbx/box/tasks.py,sha256=sVPR6a3CSU28pN6bMjZcZATYZBV7JlZvhvxcPRH8-MI,10514
70
70
  rbx/box/testcase_extractors.py,sha256=J43eG7vpxc5nP_2yhrXJODkd4EYlV4WiYVbM6hzipY4,11944
71
- rbx/box/testcase_utils.py,sha256=lvQ58D-R8vYx5AFA9sJ9xMIZJvtjYjisTpAjMIoyG4Y,7686
71
+ rbx/box/testcase_utils.py,sha256=0HydSzmy1Olqws6iPMkX6dnPnl45sPZf83SlrjQxs40,7816
72
72
  rbx/box/testcases/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
73
  rbx/box/testcases/main.py,sha256=_I7h_obRcpNLRQ6dDJDIE5NAvTyn5nBOhdsBhRA_PvU,5442
74
74
  rbx/box/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
75
75
  rbx/box/ui/captured_log.py,sha256=HPoV6LZohuECmjoqOdM_xFZbpICzQnI4n3rF-lf_b1Q,11634
76
- rbx/box/ui/css/app.tcss,sha256=LuP8dRgjk8zg68AV0cIty8RepGkTXR264AxRpflsSto,1845
77
- rbx/box/ui/main.py,sha256=0PENSCpk3zLLiG39lfdPSdLx3lCEIAXM_xpzyaDnQKs,1144
76
+ rbx/box/ui/css/app.tcss,sha256=gNNB1sG0mwMleZxP285RVvNy7SksLR3cnZejiPoWoOA,2115
77
+ rbx/box/ui/main.py,sha256=uTFqHvRHuNIz_ZNkvIS68Z1tpQYdl77iX8gHbfn6YOk,1680
78
78
  rbx/box/ui/screens/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
79
79
  rbx/box/ui/screens/build.py,sha256=DtABbf8mJNLn0tCjs5SA9wYBlbxble5-1JKQDlqzphk,156
80
80
  rbx/box/ui/screens/command.py,sha256=s29xHhvdQhgr9_E7LjggoKmSw3UuQQrJV3UQNnK97Mk,976
81
+ rbx/box/ui/screens/differ.py,sha256=Sp6xQwsFiqMl0tvbdhiPdh8AWUNPT8jshce3H7DhPrw,702
81
82
  rbx/box/ui/screens/error.py,sha256=k6Rs5maR_APKepMtPcDMSXo6BDKrP-pAnFFJgqmXDKE,496
82
83
  rbx/box/ui/screens/run.py,sha256=eKIfFRi-VbqKDxlhZDuOEdVl91XCndnmNIZyj_LMV0c,6028
83
84
  rbx/box/ui/screens/run_explorer.py,sha256=Q6VUhiqBaKr70bqA7pV5qKIQamLqUBCtxZnX2tDkap0,2651
@@ -87,12 +88,13 @@ rbx/box/ui/screens/test_explorer.py,sha256=p_MQmTvmNsB5j2E-CwN8G1mS756aWSMWGonkw
87
88
  rbx/box/ui/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
88
89
  rbx/box/ui/utils/run_ui.py,sha256=457xNZteBTb82Rcg5Cr3AmgBIHLOmRrF4BKmCRyxq3Q,3113
89
90
  rbx/box/ui/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
91
+ rbx/box/ui/widgets/diff_box.py,sha256=4OxgwOvdm9nSVUKeKLzOQBlBweFu8LiTfp6g4ZkS5nM,1125
90
92
  rbx/box/ui/widgets/file_log.py,sha256=3wlrmkWR-EMibwlwXOJ5sGpTFwFEkRaGYo5fdmf8L3w,1704
91
93
  rbx/box/ui/widgets/interaction_box.py,sha256=WDHmpO5ck21y6hNdUVxvJ81jEg9TVJh39hFJUc5-Dos,1572
92
94
  rbx/box/ui/widgets/rich_log_box.py,sha256=mF565c_Y3RYUZ_GJEFj5Eb86SFjsib31wE5qu1K0UBM,91
93
95
  rbx/box/ui/widgets/test_output_box.py,sha256=yqeAb-JFvVx1Q7w_qJbDMWQigyGy4ofGECDxQ7P0_2s,3864
94
96
  rbx/box/ui/widgets/two_sided_test_output_box.py,sha256=L-ORiDwd6CP5DFpavrKGBaX0ZHkSoQqbJrGZ4BdFUWc,2289
95
- rbx/box/unit.py,sha256=PY96t8qnsHLsoJVanbDnrIx-s8Dada9Fj_v375MhvTw,6477
97
+ rbx/box/unit.py,sha256=4pkuGQ9MpRMuKznXr1hcRls_dWLZyQ_UMHOpZSesuXw,6428
96
98
  rbx/box/validators.py,sha256=oqlNhw7jivbbH5l8g3xwihPRy76AM7MA3G4A8nyI_V0,10416
97
99
  rbx/box/validators_test.py,sha256=WY4Ho-wlsPHc0YNuz0KFVd6KQ9ouuiou3w5_zMOZ4Fs,362
98
100
  rbx/checker.py,sha256=pj1jO3my48ru-qugbER5onccANCjoR0-PaFe3H3VGEY,4118
@@ -212,8 +214,8 @@ rbx/testcase.py,sha256=yKOq3CAJZ1YTmInvnoIs0u1iJnRj_X85XiWbLI-p9d8,1951
212
214
  rbx/testcase_rendering.py,sha256=nfmv6dSEqd4aR3TsaODwkKGK6AXty_DDKtWf_ejiQpI,2084
213
215
  rbx/testing_utils.py,sha256=x_PqD8Zd2PkN91NxVHUnSTs044-1WK5KKtttKQBXpFs,2083
214
216
  rbx/utils.py,sha256=SfR844_i0ebRDMkmS_w1YdZiWPc6h2RGADygewlWRbA,4845
215
- rbx_cp-0.5.64.dist-info/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
216
- rbx_cp-0.5.64.dist-info/METADATA,sha256=rBpsAACES-Q_TSMl1doYvu3m29ymEtfkYx62vfrI75E,3604
217
- rbx_cp-0.5.64.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
218
- rbx_cp-0.5.64.dist-info/entry_points.txt,sha256=qBTLBOeifT1F00LWaEewRRE_jQPgvH7BUdJfZ-dYsFU,57
219
- rbx_cp-0.5.64.dist-info/RECORD,,
217
+ rbx_cp-0.5.66.dist-info/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
218
+ rbx_cp-0.5.66.dist-info/METADATA,sha256=jWKVFB_mcg1jfCuJqlpCGFb7MK1xCome8u0JzoNlgzI,3604
219
+ rbx_cp-0.5.66.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
220
+ rbx_cp-0.5.66.dist-info/entry_points.txt,sha256=qBTLBOeifT1F00LWaEewRRE_jQPgvH7BUdJfZ-dYsFU,57
221
+ rbx_cp-0.5.66.dist-info/RECORD,,