shrinkray 25.12.26.2__py3-none-any.whl → 25.12.27.0__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.
shrinkray/__main__.py CHANGED
@@ -271,20 +271,20 @@ def main(
271
271
  if not backup:
272
272
  backup = filename + os.extsep + "bak"
273
273
 
274
- state_kwargs: dict[str, Any] = dict(
275
- input_type=input_type,
276
- in_place=in_place,
277
- test=test,
278
- timeout=timeout,
279
- base=os.path.basename(filename),
280
- parallelism=parallelism,
281
- filename=filename,
282
- formatter=formatter,
283
- trivial_is_error=trivial_is_error,
284
- seed=seed,
285
- volume=volume,
286
- clang_delta_executable=clang_delta_executable,
287
- )
274
+ state_kwargs: dict[str, Any] = {
275
+ "input_type": input_type,
276
+ "in_place": in_place,
277
+ "test": test,
278
+ "timeout": timeout,
279
+ "base": os.path.basename(filename),
280
+ "parallelism": parallelism,
281
+ "filename": filename,
282
+ "formatter": formatter,
283
+ "trivial_is_error": trivial_is_error,
284
+ "seed": seed,
285
+ "volume": volume,
286
+ "clang_delta_executable": clang_delta_executable,
287
+ }
288
288
 
289
289
  state: ShrinkRayState[Any]
290
290
  ui: ShrinkRayUI[Any]
shrinkray/passes/sat.py CHANGED
@@ -165,14 +165,14 @@ async def renumber_variables(problem: ReductionProblem[SAT]) -> None:
165
165
  result: SAT = []
166
166
  for clause in sat:
167
167
  new_clause: Clause = sorted(
168
- set(
169
- [
168
+ {
169
+ (
170
170
  (renumbering[lit] if lit > 0 else -renumbering[-lit])
171
171
  if abs(lit) in renumbering
172
172
  else lit
173
- for lit in clause
174
- ]
175
- )
173
+ )
174
+ for lit in clause
175
+ }
176
176
  )
177
177
  if len(set(map(abs, new_clause))) == len(new_clause):
178
178
  result.append(new_clause)
@@ -290,7 +290,7 @@ class BooleanEquivalence(UnionFind[int]):
290
290
 
291
291
  def find(self, value: int) -> int:
292
292
  if not value:
293
- raise ValueError("Invalid variable %r" % (value,))
293
+ raise ValueError(f"Invalid variable {value!r}")
294
294
  return super().find(value)
295
295
 
296
296
  def merge(self, left: int, right: int) -> None:
shrinkray/reducer.py CHANGED
@@ -59,7 +59,9 @@ class Reducer[T](ABC):
59
59
  # Optional pass statistics tracking (implemented by ShrinkRay)
60
60
  pass_stats: "PassStatsTracker | None" = attrs.field(default=None, init=False)
61
61
  # Optional current pass tracking (implemented by ShrinkRay)
62
- current_reduction_pass: "ReductionPass[T] | None" = attrs.field(default=None, init=False)
62
+ current_reduction_pass: "ReductionPass[T] | None" = attrs.field(
63
+ default=None, init=False
64
+ )
63
65
 
64
66
  @contextmanager
65
67
  def backtrack(self, restart: T) -> Generator[None, None, None]:
@@ -192,7 +194,9 @@ class ShrinkRay(Reducer[bytes]):
192
194
  # Pass control: disabled passes and skip functionality
193
195
  disabled_passes: set[str] = attrs.Factory(set)
194
196
  _skip_requested: bool = attrs.field(default=False, init=False)
195
- _current_pass_scope: "trio.CancelScope | None" = attrs.field(default=None, init=False)
197
+ _current_pass_scope: "trio.CancelScope | None" = attrs.field(
198
+ default=None, init=False
199
+ )
196
200
  _passes_were_skipped: bool = attrs.field(default=False, init=False)
197
201
 
198
202
  def disable_pass(self, pass_name: str) -> None:
shrinkray/state.py CHANGED
@@ -105,12 +105,12 @@ class ShrinkRayState[TestCase](ABC):
105
105
  else:
106
106
  command = self.test
107
107
 
108
- kwargs: dict[str, Any] = dict(
109
- universal_newlines=False,
110
- preexec_fn=os.setsid,
111
- cwd=cwd,
112
- check=False,
113
- )
108
+ kwargs: dict[str, Any] = {
109
+ "universal_newlines": False,
110
+ "preexec_fn": os.setsid,
111
+ "cwd": cwd,
112
+ "check": False,
113
+ }
114
114
  if self.input_type.enabled(self._InputType.stdin) and not os.path.isdir(
115
115
  working
116
116
  ):
@@ -562,10 +562,10 @@ class ShrinkRayDirectoryState(ShrinkRayState[dict[str, bytes]]):
562
562
  sorted((k, shortlex(v)) for k, v in test_case.items()),
563
563
  )
564
564
 
565
- return dict(
566
- sort_key=dict_sort_key,
567
- size=dict_size,
568
- )
565
+ return {
566
+ "sort_key": dict_sort_key,
567
+ "size": dict_size,
568
+ }
569
569
 
570
570
  def new_reducer(
571
571
  self, problem: ReductionProblem[dict[str, bytes]]
@@ -4,7 +4,7 @@ import asyncio
4
4
  import sys
5
5
  import traceback
6
6
  import uuid
7
- from collections.abc import AsyncIterator
7
+ from collections.abc import AsyncGenerator
8
8
  from typing import Any
9
9
 
10
10
  from shrinkray.subprocess.protocol import (
@@ -200,7 +200,7 @@ class SubprocessClient:
200
200
  traceback.print_exc()
201
201
  return Response(id="", error="Failed to skip pass")
202
202
 
203
- async def get_progress_updates(self) -> AsyncIterator[ProgressUpdate]:
203
+ async def get_progress_updates(self) -> AsyncGenerator[ProgressUpdate, None]:
204
204
  """Yield progress updates as they arrive."""
205
205
  while not self._completed:
206
206
  try:
@@ -4,6 +4,7 @@ import os
4
4
  import sys
5
5
  import time
6
6
  import traceback
7
+ from contextlib import aclosing
7
8
  from typing import Any, Protocol
8
9
 
9
10
  import trio
@@ -25,6 +26,7 @@ class InputStream(Protocol):
25
26
 
26
27
  def __aiter__(self) -> "InputStream": ...
27
28
  async def __anext__(self) -> bytes | bytearray: ...
29
+ async def aclose(self) -> None: ...
28
30
 
29
31
 
30
32
  class OutputStream(Protocol):
@@ -88,12 +90,13 @@ class ReducerWorker:
88
90
  stream = trio.lowlevel.FdStream(os.dup(sys.stdin.fileno()))
89
91
 
90
92
  buffer = b""
91
- async for chunk in stream:
92
- buffer += chunk
93
- while b"\n" in buffer:
94
- line, buffer = buffer.split(b"\n", 1)
95
- if line:
96
- await self.handle_line(line.decode("utf-8"))
93
+ async with aclosing(stream) as aiter:
94
+ async for chunk in aiter:
95
+ buffer += chunk
96
+ while b"\n" in buffer:
97
+ line, buffer = buffer.split(b"\n", 1)
98
+ if line:
99
+ await self.handle_line(line.decode("utf-8"))
97
100
 
98
101
  async def handle_line(self, line: str) -> None:
99
102
  """Handle a single command line."""
@@ -124,7 +127,9 @@ class ReducerWorker:
124
127
  case "skip_pass":
125
128
  return self._handle_skip_pass(request.id)
126
129
  case _:
127
- return Response(id=request.id, error=f"Unknown command: {request.command}")
130
+ return Response(
131
+ id=request.id, error=f"Unknown command: {request.command}"
132
+ )
128
133
 
129
134
  async def _handle_start(self, request_id: str, params: dict) -> Response:
130
135
  """Start the reduction process."""
@@ -181,20 +186,20 @@ class ReducerWorker:
181
186
  if clang_delta_path:
182
187
  clang_delta_executable = ClangDelta(clang_delta_path)
183
188
 
184
- state_kwargs: dict[str, Any] = dict(
185
- input_type=input_type,
186
- in_place=in_place,
187
- test=test,
188
- timeout=timeout,
189
- base=os.path.basename(filename),
190
- parallelism=parallelism,
191
- filename=filename,
192
- formatter=formatter,
193
- trivial_is_error=trivial_is_error,
194
- seed=seed,
195
- volume=volume,
196
- clang_delta_executable=clang_delta_executable,
197
- )
189
+ state_kwargs: dict[str, Any] = {
190
+ "input_type": input_type,
191
+ "in_place": in_place,
192
+ "test": test,
193
+ "timeout": timeout,
194
+ "base": os.path.basename(filename),
195
+ "parallelism": parallelism,
196
+ "filename": filename,
197
+ "formatter": formatter,
198
+ "trivial_is_error": trivial_is_error,
199
+ "seed": seed,
200
+ "volume": volume,
201
+ "clang_delta_executable": clang_delta_executable,
202
+ }
198
203
 
199
204
  if os.path.isdir(filename):
200
205
  files = [os.path.join(d, f) for d, _, fs in os.walk(filename) for f in fs]
@@ -263,7 +268,9 @@ class ReducerWorker:
263
268
 
264
269
  if self.reducer is not None and hasattr(self.reducer, "disable_pass"):
265
270
  self.reducer.disable_pass(pass_name)
266
- return Response(id=request_id, result={"status": "disabled", "pass_name": pass_name})
271
+ return Response(
272
+ id=request_id, result={"status": "disabled", "pass_name": pass_name}
273
+ )
267
274
  return Response(id=request_id, error="Reducer does not support pass control")
268
275
 
269
276
  def _handle_enable_pass(self, request_id: str, params: dict) -> Response:
@@ -281,7 +288,9 @@ class ReducerWorker:
281
288
 
282
289
  if self.reducer is not None and hasattr(self.reducer, "enable_pass"):
283
290
  self.reducer.enable_pass(pass_name)
284
- return Response(id=request_id, result={"status": "enabled", "pass_name": pass_name})
291
+ return Response(
292
+ id=request_id, result={"status": "enabled", "pass_name": pass_name}
293
+ )
285
294
  return Response(id=request_id, error="Reducer does not support pass control")
286
295
 
287
296
  def _handle_skip_pass(self, request_id: str) -> Response:
shrinkray/tui.py CHANGED
@@ -1,7 +1,9 @@
1
1
  """Textual-based TUI for Shrink Ray."""
2
2
 
3
3
  import os
4
- from collections.abc import AsyncIterator
4
+ import traceback
5
+ from collections.abc import AsyncGenerator
6
+ from contextlib import aclosing
5
7
  from datetime import timedelta
6
8
  from typing import Literal, Protocol
7
9
 
@@ -114,7 +116,7 @@ class ReductionClientProtocol(Protocol):
114
116
 
115
117
  @property
116
118
  def error_message(self) -> str | None: ...
117
- def get_progress_updates(self) -> AsyncIterator[ProgressUpdate]: ...
119
+ def get_progress_updates(self) -> AsyncGenerator[ProgressUpdate, None]: ...
118
120
  @property
119
121
  def is_completed(self) -> bool: ...
120
122
 
@@ -485,7 +487,9 @@ class PassStatsScreen(ModalScreen[None]):
485
487
  reductions = str(ps.successful_reductions)
486
488
  success = f"{ps.success_rate:.1f}%"
487
489
 
488
- table.add_row(checkbox, name, runs, bytes_del, tests, reductions, success)
490
+ table.add_row(
491
+ checkbox, name, runs, bytes_del, tests, reductions, success
492
+ )
489
493
 
490
494
  # Restore cursor and scroll position after rebuilding
491
495
  # Only restore if the saved position is still valid
@@ -510,7 +514,9 @@ class PassStatsScreen(ModalScreen[None]):
510
514
  # Update footer with disabled count
511
515
  disabled_count = len(self.disabled_passes)
512
516
  if disabled_count > 0:
513
- footer_text = f"Showing {len(self.pass_stats)} passes ({disabled_count} disabled)"
517
+ footer_text = (
518
+ f"Showing {len(self.pass_stats)} passes ({disabled_count} disabled)"
519
+ )
514
520
  else:
515
521
  footer_text = f"Showing {len(self.pass_stats)} passes in run order"
516
522
  footer = self.query_one("#stats-footer", Static)
@@ -713,18 +719,21 @@ class ShrinkRayApp(App[None]):
713
719
  stats_display = self.query_one("#stats-display", StatsDisplay)
714
720
  content_preview = self.query_one("#content-preview", ContentPreview)
715
721
 
716
- async for update in self._client.get_progress_updates():
717
- stats_display.update_stats(update)
718
- content_preview.update_content(update.content_preview, update.hex_mode)
719
- self._latest_pass_stats = update.pass_stats
720
- self._current_pass_name = update.current_pass_name
721
- self._disabled_passes = update.disabled_passes
722
+ async with aclosing(self._client.get_progress_updates()) as updates:
723
+ async for update in updates:
724
+ stats_display.update_stats(update)
725
+ content_preview.update_content(
726
+ update.content_preview, update.hex_mode
727
+ )
728
+ self._latest_pass_stats = update.pass_stats
729
+ self._current_pass_name = update.current_pass_name
730
+ self._disabled_passes = update.disabled_passes
722
731
 
723
- # Check if all passes are disabled
724
- self._check_all_passes_disabled()
732
+ # Check if all passes are disabled
733
+ self._check_all_passes_disabled()
725
734
 
726
- if self._client.is_completed:
727
- break
735
+ if self._client.is_completed:
736
+ break
728
737
 
729
738
  self._completed = True
730
739
 
@@ -739,6 +748,7 @@ class ShrinkRayApp(App[None]):
739
748
  self.update_status("Reduction completed! Press 'q' to exit.")
740
749
 
741
750
  except Exception as e:
751
+ traceback.print_exc()
742
752
  self.exit(return_code=1, message=f"Error: {e}")
743
753
  finally:
744
754
  if self._owns_client and self._client:
@@ -750,7 +760,7 @@ class ShrinkRayApp(App[None]):
750
760
  all_pass_names = {ps.pass_name for ps in self._latest_pass_stats}
751
761
  if all_pass_names and all_pass_names <= set(self._disabled_passes):
752
762
  self.update_status(
753
- "Reduction paused (all passes disabled) - " "[p] to re-enable passes"
763
+ "Reduction paused (all passes disabled) - [p] to re-enable passes"
754
764
  )
755
765
 
756
766
  def update_status(self, message: str) -> None:
shrinkray/work.py CHANGED
@@ -13,7 +13,7 @@ Key concepts:
13
13
 
14
14
  import heapq
15
15
  from collections.abc import Awaitable, Callable, Sequence
16
- from contextlib import asynccontextmanager
16
+ from contextlib import aclosing, asynccontextmanager
17
17
  from enum import IntEnum
18
18
  from itertools import islice
19
19
  from random import Random
@@ -100,8 +100,9 @@ class WorkContext:
100
100
  async with parallel_map(
101
101
  values, f, parallelism=min(self.parallelism, n)
102
102
  ) as result:
103
- async for v in result:
104
- await send.send(v)
103
+ async with aclosing(result) as aiter:
104
+ async for v in aiter:
105
+ await send.send(v)
105
106
 
106
107
  n *= 2
107
108
  else:
@@ -122,9 +123,10 @@ class WorkContext:
122
123
  @nursery.start_soon
123
124
  async def _():
124
125
  async with self.map(ls, apply) as results:
125
- async for x, v in results:
126
- if v:
127
- await send.send(x)
126
+ async with aclosing(results) as aiter:
127
+ async for x, v in aiter:
128
+ if v:
129
+ await send.send(x)
128
130
  send.close()
129
131
 
130
132
  yield receive
@@ -139,8 +141,9 @@ class WorkContext:
139
141
  Will run in parallel if parallelism is enabled.
140
142
  """
141
143
  async with self.filter(ls, f) as filtered:
142
- async for x in filtered:
143
- return x
144
+ async with aclosing(filtered) as aiter:
145
+ async for x in aiter:
146
+ return x
144
147
  raise NotFound()
145
148
 
146
149
  async def find_large_integer(self, f: Callable[[int], Awaitable[bool]]) -> int:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shrinkray
3
- Version: 25.12.26.2
3
+ Version: 25.12.27.0
4
4
  Summary: Shrink Ray
5
5
  Author-email: "David R. MacIver" <david@drmaciver.com>
6
6
  License: MIT
@@ -1,16 +1,16 @@
1
1
  shrinkray/__init__.py,sha256=b5MvcvhsEGYya3GRXNbCJAlAL5IZHSsETLK_vtfmXRY,18
2
- shrinkray/__main__.py,sha256=sRYLrG-7FMa-y067JyYmLZMOkO2FJ2V-BenxqtBwQj0,10887
2
+ shrinkray/__main__.py,sha256=AL1WNfohkA5Uu7uiCQRhNjzfUc4qKC-UjMTDraPlT8I,10919
3
3
  shrinkray/cli.py,sha256=1-qjaIchyCDd-YCdGWtK7q9j9qr6uX6AqtwW8m5QCQg,1697
4
4
  shrinkray/display.py,sha256=WYN05uqmUVpZhwi2pxr1U-wLHWZ9KiL0RUlTCBJ1N3E,2430
5
5
  shrinkray/formatting.py,sha256=tXCGnhJn-WJGpHMaLHRCAXK8aKJBbrOdiW9QGERrQEk,3121
6
6
  shrinkray/problem.py,sha256=Kp7QN10E4tzjdpoqJve8_RT26VpywzQwY0gX2VkBGCo,17277
7
7
  shrinkray/process.py,sha256=-eP8h5X0ESbkcTic8FFEzkd4-vwaZ0YI5tLxUR25L8U,1599
8
8
  shrinkray/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- shrinkray/reducer.py,sha256=66Q5BjTLKamO2M04i2CSrbThp7PyGTRu63_ueQnjc7g,19849
10
- shrinkray/state.py,sha256=_-gyAkUm0vEdF1U0Fz_Deykj-kY2u3nGn3X6BfH3viA,22371
11
- shrinkray/tui.py,sha256=HtvqimSr1r7IX_fukRsCsVxyhEdyj2W-HLqjVATt1eM,31235
9
+ shrinkray/reducer.py,sha256=xhLo_GF7qrIVoiLHed6Wt4nxjdE-9jj_7K9F76un89o,19877
10
+ shrinkray/state.py,sha256=jqAKlirXjKK6smA2G1BnGjjoH7NgVokQhER2Ax18q64,22381
11
+ shrinkray/tui.py,sha256=YiiCDVxGl5g5TlcZN62NMYiD_IqRV68qoRDkNcasvc4,31540
12
12
  shrinkray/ui.py,sha256=xuDUwU-MM3AetvwUB7bfzav0P_drUsBrKFPhON_Nr-k,2251
13
- shrinkray/work.py,sha256=DXeqJTB_G8r7e8vrsMW2J56CJ-nhgymKBH55DT8SXs8,7901
13
+ shrinkray/work.py,sha256=GEZ14Kk3bvwUxAnACvY-wom2lVWaGrELMNxrDjv03dk,8110
14
14
  shrinkray/passes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  shrinkray/passes/bytes.py,sha256=pX2kBeH38SM4j55f8bNF_2PVBCDo6XdUj73EvOFH9f8,23904
16
16
  shrinkray/passes/clangdelta.py,sha256=t9EQ_kc159HRs48JwB5JvlJCsiCscrZgf2nhHCZRZX0,8419
@@ -19,15 +19,15 @@ shrinkray/passes/genericlanguages.py,sha256=qbTuJgUieHqWtf7cly2tm0qdbrVXzVoWcAnF
19
19
  shrinkray/passes/json.py,sha256=AcmroHgb38Aa3aApwcuYNzmyya_vnCi2RFSVqomiDg8,2586
20
20
  shrinkray/passes/patching.py,sha256=1uOTir3IbywKmsg6IIhSnxHFovZTdUCS-8PSwzgza00,8936
21
21
  shrinkray/passes/python.py,sha256=3WN1lZTf5oVL8FCTGomhrCuE04wIX9ocKcmFV86NMZA,6875
22
- shrinkray/passes/sat.py,sha256=2FGMM4rh4AX1BVEbry082C4aLCOEOXlfr3exHbxYgSQ,19514
22
+ shrinkray/passes/sat.py,sha256=5Zv4IgGfg3SYplMAAaPLkbBNh4fuVpci4F9GdVacayA,19504
23
23
  shrinkray/passes/sequences.py,sha256=jCK1fWBxCz79u7JWSps9wf7Yru7W_FAsJwdgg--CLxU,3040
24
24
  shrinkray/subprocess/__init__.py,sha256=FyV2y05uwQ1RTZGwREI0aAVaLX1jiwRcWsdsksFmdbM,451
25
- shrinkray/subprocess/client.py,sha256=xSFqm5UyQT0WJ5aBVVkuiWDsHjZYv7RqjgrjjyX0rK0,9269
25
+ shrinkray/subprocess/client.py,sha256=i2CJ7xU8_jklqDl-yphpIv-mwD0gG93jVzEDEtsdWvE,9277
26
26
  shrinkray/subprocess/protocol.py,sha256=LuHl0IkKpDzYhAGZz_EiTHNqDNq_v1ozg5aUSl7UzE4,6203
27
- shrinkray/subprocess/worker.py,sha256=MbubmnuXNFxD_SRKdiFDkGhdkCEgRxdn0tPN0HoJpyk,18998
28
- shrinkray-25.12.26.2.dist-info/licenses/LICENSE,sha256=iMKX79AuokJfIZUnGUARdUp30vVAoIPOJ7ek8TY63kk,1072
29
- shrinkray-25.12.26.2.dist-info/METADATA,sha256=3EX6HRC0LFouM2oCEKcOCjB1K1yfN1O0LrvT7LlWHh8,9693
30
- shrinkray-25.12.26.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
- shrinkray-25.12.26.2.dist-info/entry_points.txt,sha256=wIZvnGyOdVeaLTiv2klnSyTe-EKkkwn4SwHh9bmJ7qk,104
32
- shrinkray-25.12.26.2.dist-info/top_level.txt,sha256=fLif8-rFoFOnf5h8-vs3ECkKNWQopTQh3xvl1s7pchQ,10
33
- shrinkray-25.12.26.2.dist-info/RECORD,,
27
+ shrinkray/subprocess/worker.py,sha256=IyO9FNQ11vWwrc2ikH-Ca3iGoc03MJOIRNUqK3R06aE,19269
28
+ shrinkray-25.12.27.0.dist-info/licenses/LICENSE,sha256=iMKX79AuokJfIZUnGUARdUp30vVAoIPOJ7ek8TY63kk,1072
29
+ shrinkray-25.12.27.0.dist-info/METADATA,sha256=jdpwOHRfJE7bJnBFBbYlpEw7bINLJaU_v2Pz_aBGsmw,9693
30
+ shrinkray-25.12.27.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
+ shrinkray-25.12.27.0.dist-info/entry_points.txt,sha256=wIZvnGyOdVeaLTiv2klnSyTe-EKkkwn4SwHh9bmJ7qk,104
32
+ shrinkray-25.12.27.0.dist-info/top_level.txt,sha256=fLif8-rFoFOnf5h8-vs3ECkKNWQopTQh3xvl1s7pchQ,10
33
+ shrinkray-25.12.27.0.dist-info/RECORD,,