shrinkray 25.12.27.3__py3-none-any.whl → 25.12.28.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/passes/bytes.py CHANGED
@@ -28,26 +28,6 @@ from shrinkray.passes.patching import Cuts, Patches, apply_patches
28
28
  from shrinkray.problem import Format, ReductionProblem
29
29
 
30
30
 
31
- @define(frozen=True)
32
- class Encoding(Format[bytes, str]):
33
- """Format that decodes/encodes bytes using a character encoding."""
34
-
35
- encoding: str
36
-
37
- def __repr__(self) -> str:
38
- return f"Encoding({repr(self.encoding)})"
39
-
40
- @property
41
- def name(self) -> str:
42
- return self.encoding
43
-
44
- def parse(self, input: bytes) -> str:
45
- return input.decode(self.encoding)
46
-
47
- def dumps(self, input: str) -> bytes:
48
- return input.encode(self.encoding)
49
-
50
-
51
31
  @define(frozen=True)
52
32
  class Split(Format[bytes, list[bytes]]):
53
33
  """Format that splits bytes by a delimiter.
@@ -165,9 +145,6 @@ def tokenize(text: bytes) -> list[bytes]:
165
145
  return result
166
146
 
167
147
 
168
- MAX_DELETE_INTERVAL = 8
169
-
170
-
171
148
  async def lexeme_based_deletions(
172
149
  problem: ReductionProblem[bytes], min_size: int = 8
173
150
  ) -> None:
@@ -601,108 +578,6 @@ async def lower_individual_bytes(problem: ReductionProblem[bytes]) -> None:
601
578
  await apply_patches(problem, IndividualByteReplacement(), patches)
602
579
 
603
580
 
604
- RegionReplacementPatch = list[tuple[int, int, int]]
605
-
606
-
607
- class RegionReplacement(Patches[RegionReplacementPatch, bytes]):
608
- @property
609
- def empty(self) -> RegionReplacementPatch:
610
- return []
611
-
612
- def combine(self, *patches: RegionReplacementPatch) -> RegionReplacementPatch:
613
- result: RegionReplacementPatch = []
614
- for p in patches:
615
- result.extend(p)
616
- return result
617
-
618
- def apply(self, patch: RegionReplacementPatch, target: bytes) -> bytes:
619
- result = bytearray(target)
620
- for i, j, d in patch:
621
- if d < result[i]:
622
- for k in range(i, j):
623
- result[k] = d
624
- return bytes(result)
625
-
626
- def size(self, patch: RegionReplacementPatch) -> int:
627
- return 0
628
-
629
-
630
- async def short_replacements(problem: ReductionProblem[bytes]) -> None:
631
- """Replace short regions with uniform byte values.
632
-
633
- Tries replacing 1-4 byte regions with uniform values like 0, 1,
634
- space, newline, or period. Useful for simplifying small sequences.
635
- """
636
- target = problem.current_test_case
637
- patches = [
638
- [(i, j, c)]
639
- for c in [0, 1] + list(b"01 \t\n\r.")
640
- for i in range(len(target))
641
- if target[i] > c
642
- for j in range(i + 1, min(i + 5, len(target) + 1))
643
- ]
644
-
645
- await apply_patches(problem, RegionReplacement(), patches)
646
-
647
-
648
- WHITESPACE = b" \t\r\n"
649
-
650
-
651
- async def sort_whitespace(problem: ReductionProblem[bytes]) -> None:
652
- """NB: This is a stupid pass that we only really need for artificial
653
- test cases, but it's helpful for allowing those artificial test cases
654
- to expose other issues."""
655
-
656
- whitespace_up_to = 0
657
- while (
658
- whitespace_up_to < len(problem.current_test_case)
659
- and problem.current_test_case[whitespace_up_to] not in WHITESPACE
660
- ):
661
- whitespace_up_to += 1
662
- while (
663
- whitespace_up_to < len(problem.current_test_case)
664
- and problem.current_test_case[whitespace_up_to] in WHITESPACE
665
- ):
666
- whitespace_up_to += 1
667
-
668
- # If the initial whitespace ends with a newline we want to keep it doing
669
- # that. This is mostly for Python purposes.
670
- if (
671
- whitespace_up_to > 0
672
- and problem.current_test_case[whitespace_up_to - 1] == b"\n"[0]
673
- ):
674
- whitespace_up_to -= 1
675
-
676
- i = whitespace_up_to + 1
677
-
678
- while i < len(problem.current_test_case):
679
- if problem.current_test_case[i] not in WHITESPACE:
680
- i += 1
681
- continue
682
-
683
- async def can_move_to_whitespace(k: int) -> bool:
684
- if i + k > len(problem.current_test_case):
685
- return False
686
-
687
- base = problem.current_test_case
688
- target = base[i : i + k]
689
-
690
- if any(c not in WHITESPACE for c in target):
691
- return False
692
-
693
- prefix = base[:whitespace_up_to]
694
- attempt = prefix + target + base[whitespace_up_to:i] + base[i + k :]
695
- return await problem.is_interesting(attempt)
696
-
697
- k = await problem.work.find_large_integer(can_move_to_whitespace)
698
- whitespace_up_to += k
699
- i += k + 1
700
- test_case = problem.current_test_case
701
- await problem.is_interesting(
702
- bytes(sorted(test_case[:whitespace_up_to])) + test_case[whitespace_up_to:]
703
- )
704
-
705
-
706
581
  # These are some cheat substitutions that are sometimes helpful, but mostly
707
582
  # for passing stupid tests.
708
583
  STANDARD_SUBSTITUTIONS = [(b"\0\0", b"\1"), (b"\0\0", b"\xff")]
@@ -1,6 +1,5 @@
1
1
  import os
2
2
  import subprocess
3
- from functools import lru_cache
4
3
  from glob import glob
5
4
  from shutil import which
6
5
  from tempfile import NamedTemporaryFile
@@ -26,29 +25,6 @@ def find_clang_delta():
26
25
  return clang_delta
27
26
 
28
27
 
29
- @lru_cache(maxsize=1)
30
- def clang_delta_works() -> bool:
31
- """Check if clang_delta can actually execute.
32
-
33
- This verifies not just that the binary exists, but that it can run.
34
- On some systems (e.g., Ubuntu 24.04), creduce is installed but
35
- clang_delta fails at runtime due to shared library issues.
36
- """
37
- clang_delta = find_clang_delta()
38
- if not clang_delta:
39
- return False
40
- try:
41
- # Run a simple test to verify clang_delta works
42
- result = subprocess.run(
43
- [clang_delta, "--help"],
44
- capture_output=True,
45
- timeout=5,
46
- )
47
- return result.returncode == 0
48
- except (OSError, subprocess.TimeoutExpired):
49
- return False
50
-
51
-
52
28
  TRANSFORMATIONS: list[str] = [
53
29
  "aggregate-to-scalar",
54
30
  "binop-simplification",
@@ -9,7 +9,6 @@ from string import ascii_lowercase, ascii_uppercase
9
9
  from typing import AnyStr
10
10
 
11
11
  import trio
12
- from attr import define
13
12
 
14
13
  from shrinkray.passes.bytes import ByteReplacement, delete_intervals
15
14
  from shrinkray.passes.definitions import ReductionPass
@@ -24,25 +23,6 @@ from shrinkray.problem import (
24
23
  from shrinkray.work import NotFound
25
24
 
26
25
 
27
- @define(frozen=True)
28
- class Substring(Format[AnyStr, AnyStr]):
29
- prefix: AnyStr
30
- suffix: AnyStr
31
-
32
- @property
33
- def name(self) -> str:
34
- return f"Substring({len(self.prefix)}, {len(self.suffix)})"
35
-
36
- def parse(self, input: AnyStr) -> AnyStr:
37
- if input.startswith(self.prefix) and input.endswith(self.suffix):
38
- return input[len(self.prefix) : len(input) - len(self.suffix)]
39
- else:
40
- raise ParseError()
41
-
42
- def dumps(self, input: AnyStr) -> AnyStr:
43
- return self.prefix + input + self.suffix
44
-
45
-
46
26
  class RegionReplacingPatches(Patches[dict[int, AnyStr], AnyStr]):
47
27
  def __init__(self, regions: list[tuple[int, int]]):
48
28
  assert regions
shrinkray/passes/json.py CHANGED
@@ -9,14 +9,6 @@ from shrinkray.passes.patching import Patches, apply_patches
9
9
  from shrinkray.problem import Format, ParseError, ReductionProblem
10
10
 
11
11
 
12
- def is_json(s: bytes) -> bool:
13
- try:
14
- json.loads(s)
15
- return True
16
- except ValueError:
17
- return False
18
-
19
-
20
12
  @define(frozen=True)
21
13
  class _JSON(Format[bytes, Any]):
22
14
  def __repr__(self) -> str:
@@ -1,7 +1,5 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from collections.abc import Callable, Iterable, Sequence
3
- from enum import Enum
4
- from random import Random
5
3
  from typing import Any, TypeVar, cast
6
4
 
7
5
  import trio
@@ -52,27 +50,6 @@ class SetPatches[T, TargetType](Patches[frozenset[T], TargetType]):
52
50
  return len(patch)
53
51
 
54
52
 
55
- class ListPatches[T, TargetType](Patches[list[T], TargetType]):
56
- def __init__(self, apply: Callable[[list[T], TargetType], TargetType]):
57
- self.__apply = apply
58
-
59
- @property
60
- def empty(self):
61
- return []
62
-
63
- def combine(self, *patches: list[T]) -> list[T]:
64
- result = []
65
- for p in patches:
66
- result.extend(p)
67
- return result
68
-
69
- def apply(self, patch: list[T], target: TargetType) -> TargetType:
70
- return self.__apply(patch, target)
71
-
72
- def size(self, patch: list[T]) -> int:
73
- return len(patch)
74
-
75
-
76
53
  class PatchApplier[PatchType, TargetType]:
77
54
  def __init__(
78
55
  self,
@@ -174,15 +151,6 @@ class PatchApplier[PatchType, TargetType]:
174
151
  return await receive_merge_result.receive()
175
152
 
176
153
 
177
- class Direction(Enum):
178
- LEFT = 0
179
- RIGHT = 1
180
-
181
-
182
- class Completed(Exception):
183
- pass
184
-
185
-
186
154
  async def apply_patches[PatchType, TargetType](
187
155
  problem: ReductionProblem[TargetType],
188
156
  patch_info: Patches[PatchType, TargetType],
@@ -220,37 +188,6 @@ async def apply_patches[PatchType, TargetType](
220
188
  await applier.try_apply_patch(patch)
221
189
 
222
190
 
223
- class LazyMutableRange:
224
- def __init__(self, n: int):
225
- self.__size = n
226
- self.__mask: dict[int, int] = {}
227
-
228
- def __getitem__(self, i: int) -> int:
229
- return self.__mask.get(i, i)
230
-
231
- def __setitem__(self, i: int, v: int) -> None:
232
- self.__mask[i] = v
233
-
234
- def __len__(self) -> int:
235
- return self.__size
236
-
237
- def pop(self) -> int:
238
- i = len(self) - 1
239
- result = self[i]
240
- self.__size = i
241
- self.__mask.pop(i, None)
242
- return result
243
-
244
-
245
- def lazy_shuffle[T](seq: Sequence[T], rnd: Random) -> Iterable[T]:
246
- indices = LazyMutableRange(len(seq))
247
- while indices:
248
- j = len(indices) - 1
249
- i = rnd.randrange(0, len(indices))
250
- indices[i], indices[j] = indices[j], indices[i]
251
- yield seq[indices.pop()]
252
-
253
-
254
191
  CutPatch = list[tuple[int, int]]
255
192
 
256
193
 
@@ -18,31 +18,6 @@ async def delete_elements[Seq: Sequence[Any]](problem: ReductionProblem[Seq]) ->
18
18
  )
19
19
 
20
20
 
21
- def merged_intervals(intervals: list[tuple[int, int]]) -> list[tuple[int, int]]:
22
- normalized: list[list[int]] = []
23
- for start, end in sorted(map(tuple, intervals)):
24
- if normalized and normalized[-1][-1] >= start:
25
- normalized[-1][-1] = max(normalized[-1][-1], end)
26
- else:
27
- normalized.append([start, end])
28
- return list(map(tuple, normalized)) # type: ignore
29
-
30
-
31
- def with_deletions[Seq: Sequence[Any]](
32
- target: Seq, deletions: list[tuple[int, int]]
33
- ) -> Seq:
34
- result: list[Any] = []
35
- prev = 0
36
- total_deleted = 0
37
- for start, end in deletions:
38
- total_deleted += end - start
39
- result.extend(target[prev:start])
40
- prev = end
41
- result.extend(target[prev:])
42
- assert len(result) + total_deleted == len(target)
43
- return type(target)(result) # type: ignore
44
-
45
-
46
21
  def block_deletion[Seq: Sequence[Any]](
47
22
  min_block: int, max_block: int
48
23
  ) -> ReductionPass[Seq]:
shrinkray/reducer.py CHANGED
@@ -99,50 +99,6 @@ class Reducer[T](ABC):
99
99
  """Skip the currently running pass. Override in subclasses for pass control."""
100
100
 
101
101
 
102
- @define
103
- class BasicReducer[T](Reducer[T]):
104
- reduction_passes: Iterable[ReductionPass[T]]
105
- pumps: Iterable[ReductionPump[T]] = ()
106
- _status: str = "Starting up"
107
-
108
- def __attrs_post_init__(self) -> None:
109
- self.reduction_passes = list(self.reduction_passes)
110
-
111
- @property
112
- def status(self) -> str:
113
- return self._status
114
-
115
- @status.setter
116
- def status(self, value: str) -> None:
117
- self._status = value
118
-
119
- async def run_pass(self, rp: ReductionPass[T]) -> None:
120
- await rp(self.target)
121
-
122
- async def run(self) -> None:
123
- await self.target.setup()
124
-
125
- while True:
126
- prev = self.target.current_test_case
127
- for rp in self.reduction_passes:
128
- self.status = f"Running reduction pass {rp.__name__}"
129
- await self.run_pass(rp)
130
- for pump in self.pumps:
131
- self.status = f"Pumping with {pump.__name__}"
132
- pumped = await pump(self.target)
133
- if pumped != self.target.current_test_case:
134
- with self.backtrack(pumped):
135
- for rp in self.reduction_passes:
136
- self.status = f"Running reduction pass {rp.__name__} under pump {pump.__name__}"
137
- await self.run_pass(rp)
138
- if prev == self.target.current_test_case:
139
- return
140
-
141
-
142
- class RestartPass(Exception):
143
- pass
144
-
145
-
146
102
  @define
147
103
  class PassStats:
148
104
  """Statistics for a single reduction pass."""
@@ -181,12 +137,6 @@ class PassStatsTracker:
181
137
  return list(self._stats.values())
182
138
 
183
139
 
184
- class SkipPass(Exception):
185
- """Raised to skip the current pass."""
186
-
187
- pass
188
-
189
-
190
140
  @define
191
141
  class ShrinkRay(Reducer[bytes]):
192
142
  clang_delta: ClangDelta | None = None
shrinkray/state.py CHANGED
@@ -11,7 +11,7 @@ from abc import ABC, abstractmethod
11
11
  from collections import deque
12
12
  from datetime import timedelta
13
13
  from tempfile import TemporaryDirectory
14
- from typing import Any, TypeVar
14
+ from typing import Any
15
15
 
16
16
  import humanize
17
17
  import trio
@@ -28,9 +28,6 @@ from shrinkray.reducer import DirectoryShrinkRay, Reducer, ShrinkRay
28
28
  from shrinkray.work import Volume, WorkContext
29
29
 
30
30
 
31
- T = TypeVar("T")
32
-
33
-
34
31
  class TimeoutExceededOnInitial(InvalidInitialExample):
35
32
  def __init__(self, runtime: float, timeout: float) -> None:
36
33
  self.runtime = runtime
@@ -5,9 +5,7 @@ from shrinkray.subprocess.protocol import (
5
5
  ProgressUpdate,
6
6
  Request,
7
7
  Response,
8
- decode_bytes,
9
8
  deserialize,
10
- encode_bytes,
11
9
  serialize,
12
10
  )
13
11
 
@@ -18,7 +16,5 @@ __all__ = [
18
16
  "ProgressUpdate",
19
17
  "serialize",
20
18
  "deserialize",
21
- "encode_bytes",
22
- "decode_bytes",
23
19
  "SubprocessClient",
24
20
  ]
@@ -1,6 +1,5 @@
1
1
  """Line-oriented JSON protocol for subprocess communication."""
2
2
 
3
- import base64
4
3
  import json
5
4
  from dataclasses import dataclass, field
6
5
  from typing import Any
@@ -186,13 +185,3 @@ def deserialize(line: str) -> Request | Response | ProgressUpdate:
186
185
  command=data["command"],
187
186
  params=data.get("params", {}),
188
187
  )
189
-
190
-
191
- def encode_bytes(data: bytes) -> str:
192
- """Encode bytes to base64 string for JSON transport."""
193
- return base64.b64encode(data).decode("ascii")
194
-
195
-
196
- def decode_bytes(data: str) -> bytes:
197
- """Decode base64 string back to bytes."""
198
- return base64.b64decode(data.encode("ascii"))
@@ -37,14 +37,6 @@ class OutputStream(Protocol):
37
37
  async def send(self, data: bytes) -> None: ...
38
38
 
39
39
 
40
- class StdoutStream:
41
- """Wrapper around sys.stdout for the OutputStream protocol."""
42
-
43
- async def send(self, data: bytes) -> None:
44
- sys.stdout.write(data.decode("utf-8"))
45
- sys.stdout.flush()
46
-
47
-
48
40
  class ReducerWorker:
49
41
  """Runs the reducer in a subprocess with JSON protocol communication."""
50
42
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shrinkray
3
- Version: 25.12.27.3
3
+ Version: 25.12.28.0
4
4
  Summary: Shrink Ray
5
5
  Author-email: "David R. MacIver" <david@drmaciver.com>
6
6
  License: MIT
@@ -0,0 +1,33 @@
1
+ shrinkray/__init__.py,sha256=b5MvcvhsEGYya3GRXNbCJAlAL5IZHSsETLK_vtfmXRY,18
2
+ shrinkray/__main__.py,sha256=HuDZFXgD9LH3XPBK2QBvP4YkyTFJvduK9-PVtz9fKpk,11762
3
+ shrinkray/cli.py,sha256=1-qjaIchyCDd-YCdGWtK7q9j9qr6uX6AqtwW8m5QCQg,1697
4
+ shrinkray/formatting.py,sha256=tXCGnhJn-WJGpHMaLHRCAXK8aKJBbrOdiW9QGERrQEk,3121
5
+ shrinkray/problem.py,sha256=_edENYk8OC5o_2ng0WZrhIfilhlY5IuOrqt0qWBZAuM,25979
6
+ shrinkray/process.py,sha256=-eP8h5X0ESbkcTic8FFEzkd4-vwaZ0YI5tLxUR25L8U,1599
7
+ shrinkray/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ shrinkray/reducer.py,sha256=8CF_SxkfVMBxiikQKwv-rlrQawTLhQxy6QnVwIWWiws,18601
9
+ shrinkray/state.py,sha256=V5vUyBKXudLoMe3TvGJwWf6EDzdVr2TRrmdNyU13qio,29565
10
+ shrinkray/tui.py,sha256=Ovd6Kbk_cmOYJlwnjEyl6ka_2g1qJAtwxojh5CWEzNA,33001
11
+ shrinkray/ui.py,sha256=xuDUwU-MM3AetvwUB7bfzav0P_drUsBrKFPhON_Nr-k,2251
12
+ shrinkray/validation.py,sha256=piBCO-k9he_id6TWC4EHMK3GfuyPqRcNfkNJPVjxEaU,13366
13
+ shrinkray/work.py,sha256=GEZ14Kk3bvwUxAnACvY-wom2lVWaGrELMNxrDjv03dk,8110
14
+ shrinkray/passes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ shrinkray/passes/bytes.py,sha256=2Xw6WI3d3MNJATQKpS77qywdxINlwiHt__dIdh8LCVE,20230
16
+ shrinkray/passes/clangdelta.py,sha256=0CYbBiGBpD2wlOpefe_uJnea1YU2-UMfLxKi1KPcxpY,7678
17
+ shrinkray/passes/definitions.py,sha256=TDDPimp7DE60xgidAE11npt2KicQAEi0UydIzjS9VHw,2469
18
+ shrinkray/passes/genericlanguages.py,sha256=QDUIDJbzFsAjHzTWH-sB-ioB2NhBPBtZOk8Q5ZwxA54,9859
19
+ shrinkray/passes/json.py,sha256=3EcM7bmseFZaIPUN2N8c9CZYQoKVHnYfhGG1RKrrItg,2458
20
+ shrinkray/passes/patching.py,sha256=Fo04O0hVMBLFuIP704LPCJrbjU1ITHr4lCt2HrvMsOc,7449
21
+ shrinkray/passes/python.py,sha256=3WN1lZTf5oVL8FCTGomhrCuE04wIX9ocKcmFV86NMZA,6875
22
+ shrinkray/passes/sat.py,sha256=OboY6jsKf6lph3pAFh535plvhNOVzEF8HJ66WEqsNm4,19483
23
+ shrinkray/passes/sequences.py,sha256=-5ajmMeHnS7onjjppbxLiP0F6mRSqiFI5DspBTj2x_M,2206
24
+ shrinkray/subprocess/__init__.py,sha256=qxZ19Nzizbm7H0MkKL38OqfP7U-VuOAvaqBVkmHFivY,375
25
+ shrinkray/subprocess/client.py,sha256=abBkrXaJcA6cd3l_avPuteO_kYnjU5IRG7VtlmiAJgE,9428
26
+ shrinkray/subprocess/protocol.py,sha256=di4vXIFcCSs2ej1E4Y3WeYXsNoo4VsBjEIeogf5AYQY,6336
27
+ shrinkray/subprocess/worker.py,sha256=0D719AHSF-oL5iifG6x4xYeUCMxEUZ_QXrDrogC901U,21238
28
+ shrinkray-25.12.28.0.dist-info/licenses/LICENSE,sha256=iMKX79AuokJfIZUnGUARdUp30vVAoIPOJ7ek8TY63kk,1072
29
+ shrinkray-25.12.28.0.dist-info/METADATA,sha256=pwliGh5xlyr_LLxhuqsoCnl_pl77NYLaB8EqCIt4iBU,7600
30
+ shrinkray-25.12.28.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
+ shrinkray-25.12.28.0.dist-info/entry_points.txt,sha256=wIZvnGyOdVeaLTiv2klnSyTe-EKkkwn4SwHh9bmJ7qk,104
32
+ shrinkray-25.12.28.0.dist-info/top_level.txt,sha256=fLif8-rFoFOnf5h8-vs3ECkKNWQopTQh3xvl1s7pchQ,10
33
+ shrinkray-25.12.28.0.dist-info/RECORD,,
shrinkray/display.py DELETED
@@ -1,75 +0,0 @@
1
- """Display utilities for shrink ray."""
2
-
3
- import shutil
4
- from collections.abc import Iterable
5
-
6
- from binaryornot.check import is_binary_string # type: ignore[import-not-found]
7
-
8
-
9
- def get_terminal_size() -> tuple[int, int]:
10
- """Get terminal size, with sensible fallbacks.
11
-
12
- Returns:
13
- (columns, lines) tuple. Defaults to (80, 24) if terminal size
14
- cannot be determined.
15
- """
16
- size = shutil.get_terminal_size(fallback=(80, 24))
17
- return (size.columns, size.lines)
18
-
19
-
20
- def to_lines(test_case: bytes) -> list[str]:
21
- """Convert a test case to displayable lines."""
22
- result = []
23
- for line in test_case.split(b"\n"):
24
- if is_binary_string(line):
25
- result.append(line.hex())
26
- else:
27
- try:
28
- result.append(line.decode("utf-8"))
29
- except UnicodeDecodeError:
30
- result.append(line.hex())
31
- return result
32
-
33
-
34
- def to_blocks(test_case: bytes, block_size: int | None = None) -> list[str]:
35
- """Convert a test case to hex blocks for display.
36
-
37
- Args:
38
- test_case: The bytes to convert
39
- block_size: Number of bytes per block. If None, automatically
40
- calculated from terminal width (each byte becomes 2 hex chars).
41
- """
42
- if block_size is None:
43
- columns, _ = get_terminal_size()
44
- # Each byte becomes 2 hex chars, leave some margin
45
- block_size = max(1, (columns - 4) // 2)
46
- return [
47
- test_case[i : i + block_size].hex()
48
- for i in range(0, len(test_case), block_size)
49
- ]
50
-
51
-
52
- def format_diff(diff: Iterable[str], max_lines: int | None = None) -> str:
53
- """Format a diff for display, truncating if too long.
54
-
55
- Args:
56
- diff: Iterable of diff lines
57
- max_lines: Maximum number of lines to include. If None, uses
58
- terminal height multiplied by a factor to allow scrolling
59
- through substantial context.
60
- """
61
- if max_lines is None:
62
- _, lines = get_terminal_size()
63
- # Allow multiple screenfuls of context for scrolling
64
- max_lines = max(lines * 20, 100)
65
- results = []
66
- start_writing = False
67
- for line in diff:
68
- if not start_writing and line.startswith("@@"):
69
- start_writing = True
70
- if start_writing:
71
- results.append(line)
72
- if len(results) > max_lines:
73
- results.append("...")
74
- break
75
- return "\n".join(results)
@@ -1,34 +0,0 @@
1
- shrinkray/__init__.py,sha256=b5MvcvhsEGYya3GRXNbCJAlAL5IZHSsETLK_vtfmXRY,18
2
- shrinkray/__main__.py,sha256=HuDZFXgD9LH3XPBK2QBvP4YkyTFJvduK9-PVtz9fKpk,11762
3
- shrinkray/cli.py,sha256=1-qjaIchyCDd-YCdGWtK7q9j9qr6uX6AqtwW8m5QCQg,1697
4
- shrinkray/display.py,sha256=WYN05uqmUVpZhwi2pxr1U-wLHWZ9KiL0RUlTCBJ1N3E,2430
5
- shrinkray/formatting.py,sha256=tXCGnhJn-WJGpHMaLHRCAXK8aKJBbrOdiW9QGERrQEk,3121
6
- shrinkray/problem.py,sha256=_edENYk8OC5o_2ng0WZrhIfilhlY5IuOrqt0qWBZAuM,25979
7
- shrinkray/process.py,sha256=-eP8h5X0ESbkcTic8FFEzkd4-vwaZ0YI5tLxUR25L8U,1599
8
- shrinkray/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- shrinkray/reducer.py,sha256=t2BBjq-EZ0qBbsez4ep0tFYCkDT8RwKYnjJ2fRsXubg,20096
10
- shrinkray/state.py,sha256=a8Xq7IQwLz7CyZ58zU0Z91uoglgPJmhQUvbcdgsIoKk,29593
11
- shrinkray/tui.py,sha256=Ovd6Kbk_cmOYJlwnjEyl6ka_2g1qJAtwxojh5CWEzNA,33001
12
- shrinkray/ui.py,sha256=xuDUwU-MM3AetvwUB7bfzav0P_drUsBrKFPhON_Nr-k,2251
13
- shrinkray/validation.py,sha256=piBCO-k9he_id6TWC4EHMK3GfuyPqRcNfkNJPVjxEaU,13366
14
- shrinkray/work.py,sha256=GEZ14Kk3bvwUxAnACvY-wom2lVWaGrELMNxrDjv03dk,8110
15
- shrinkray/passes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- shrinkray/passes/bytes.py,sha256=U1sNAuqUlsaLpRSJuS9X4H7S2jOiilTUIIE9-WjSd4k,23967
17
- shrinkray/passes/clangdelta.py,sha256=t9EQ_kc159HRs48JwB5JvlJCsiCscrZgf2nhHCZRZX0,8419
18
- shrinkray/passes/definitions.py,sha256=TDDPimp7DE60xgidAE11npt2KicQAEi0UydIzjS9VHw,2469
19
- shrinkray/passes/genericlanguages.py,sha256=ZqTfEHUTRbkm6PiTkBc_y3Q5Q0MJAnibNEiuKDOhiS0,10432
20
- shrinkray/passes/json.py,sha256=FydtjpVD3lMjp9OEwv5mu8CAz96ofGxIZOzt9o6eLUA,2586
21
- shrinkray/passes/patching.py,sha256=1uOTir3IbywKmsg6IIhSnxHFovZTdUCS-8PSwzgza00,8936
22
- shrinkray/passes/python.py,sha256=3WN1lZTf5oVL8FCTGomhrCuE04wIX9ocKcmFV86NMZA,6875
23
- shrinkray/passes/sat.py,sha256=OboY6jsKf6lph3pAFh535plvhNOVzEF8HJ66WEqsNm4,19483
24
- shrinkray/passes/sequences.py,sha256=jCK1fWBxCz79u7JWSps9wf7Yru7W_FAsJwdgg--CLxU,3040
25
- shrinkray/subprocess/__init__.py,sha256=FyV2y05uwQ1RTZGwREI0aAVaLX1jiwRcWsdsksFmdbM,451
26
- shrinkray/subprocess/client.py,sha256=abBkrXaJcA6cd3l_avPuteO_kYnjU5IRG7VtlmiAJgE,9428
27
- shrinkray/subprocess/protocol.py,sha256=fbY29q-j98KnAuZJ1Y_ARoi7BskkUu37SoybwVWb-pA,6636
28
- shrinkray/subprocess/worker.py,sha256=IBAXZq3E27bTuN9kHGgU5xhU6E8m_5-Ioz1dubVTuN8,21449
29
- shrinkray-25.12.27.3.dist-info/licenses/LICENSE,sha256=iMKX79AuokJfIZUnGUARdUp30vVAoIPOJ7ek8TY63kk,1072
30
- shrinkray-25.12.27.3.dist-info/METADATA,sha256=BUSNccYFT8TdtojoQHdt-YR9DEVtjsG8fNvlybnRsB0,7600
31
- shrinkray-25.12.27.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
32
- shrinkray-25.12.27.3.dist-info/entry_points.txt,sha256=wIZvnGyOdVeaLTiv2klnSyTe-EKkkwn4SwHh9bmJ7qk,104
33
- shrinkray-25.12.27.3.dist-info/top_level.txt,sha256=fLif8-rFoFOnf5h8-vs3ECkKNWQopTQh3xvl1s7pchQ,10
34
- shrinkray-25.12.27.3.dist-info/RECORD,,