phext 0.3.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.
- phext/__init__.py +1 -0
- phext/coordinate.py +154 -0
- phext/phext.py +546 -0
- phext/positionedScroll.py +11 -0
- phext/range.py +7 -0
- phext-0.3.0.dist-info/METADATA +28 -0
- phext-0.3.0.dist-info/RECORD +8 -0
- phext-0.3.0.dist-info/WHEEL +4 -0
phext/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.0"
|
phext/coordinate.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from functools import total_ordering
|
|
3
|
+
|
|
4
|
+
@total_ordering
|
|
5
|
+
@dataclass
|
|
6
|
+
class Coordinate:
|
|
7
|
+
library: int
|
|
8
|
+
shelf: int
|
|
9
|
+
series: int
|
|
10
|
+
collection: int
|
|
11
|
+
volume: int
|
|
12
|
+
book: int
|
|
13
|
+
chapter: int
|
|
14
|
+
section: int
|
|
15
|
+
scroll: int
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def from_string(ignored, coord_str: str):
|
|
19
|
+
try:
|
|
20
|
+
parts = coord_str.strip().split("/")
|
|
21
|
+
if len(parts) != 3:
|
|
22
|
+
raise ValueError("Coordinate must conform to Phext Standard Addressing")
|
|
23
|
+
|
|
24
|
+
nums = []
|
|
25
|
+
for part in parts:
|
|
26
|
+
nums.extend(int(x) for x in part.split("."))
|
|
27
|
+
|
|
28
|
+
if len(nums) != 9:
|
|
29
|
+
raise ValueError("Coordinate must have exactly 9 numeric parts")
|
|
30
|
+
|
|
31
|
+
if any(n < 1 or n > 999 for n in nums):
|
|
32
|
+
raise ValueError("All coordinate values must be between 1 and 999")
|
|
33
|
+
|
|
34
|
+
return Coordinate(nums[0], nums[1], nums[2], nums[3], nums[4], nums[5], nums[6], nums[7], nums[8])
|
|
35
|
+
|
|
36
|
+
except Exception as e:
|
|
37
|
+
raise ValueError(f"Invalid coordinate string: {coord_str} {e}") from e
|
|
38
|
+
|
|
39
|
+
def libraryBreak(self):
|
|
40
|
+
self.library += 1
|
|
41
|
+
self.shelf = 1
|
|
42
|
+
self.series = 1
|
|
43
|
+
self.collection = 1
|
|
44
|
+
self.volume = 1
|
|
45
|
+
self.book = 1
|
|
46
|
+
self.chapter = 1
|
|
47
|
+
self.section = 1
|
|
48
|
+
self.scroll = 1
|
|
49
|
+
|
|
50
|
+
def shelfBreak(self):
|
|
51
|
+
self.shelf += 1
|
|
52
|
+
self.series = 1
|
|
53
|
+
self.collection = 1
|
|
54
|
+
self.volume = 1
|
|
55
|
+
self.book = 1
|
|
56
|
+
self.chapter = 1
|
|
57
|
+
self.section = 1
|
|
58
|
+
self.scroll = 1
|
|
59
|
+
|
|
60
|
+
def seriesBreak(self):
|
|
61
|
+
self.series += 1
|
|
62
|
+
self.collection = 1
|
|
63
|
+
self.volume = 1
|
|
64
|
+
self.book = 1
|
|
65
|
+
self.chapter = 1
|
|
66
|
+
self.section = 1
|
|
67
|
+
self.scroll = 1
|
|
68
|
+
|
|
69
|
+
def collectionBreak(self):
|
|
70
|
+
self.collection += 1
|
|
71
|
+
self.volume = 1
|
|
72
|
+
self.book = 1
|
|
73
|
+
self.chapter = 1
|
|
74
|
+
self.section = 1
|
|
75
|
+
self.scroll = 1
|
|
76
|
+
|
|
77
|
+
def volumeBreak(self):
|
|
78
|
+
self.volume += 1
|
|
79
|
+
self.book = 1
|
|
80
|
+
self.chapter = 1
|
|
81
|
+
self.section = 1
|
|
82
|
+
self.scroll = 1
|
|
83
|
+
|
|
84
|
+
def bookBreak(self):
|
|
85
|
+
self.book += 1
|
|
86
|
+
self.chapter = 1
|
|
87
|
+
self.section = 1
|
|
88
|
+
self.scroll = 1
|
|
89
|
+
|
|
90
|
+
def chapterBreak(self):
|
|
91
|
+
self.chapter += 1
|
|
92
|
+
self.section = 1
|
|
93
|
+
self.scroll = 1
|
|
94
|
+
|
|
95
|
+
def sectionBreak(self):
|
|
96
|
+
self.section += 1
|
|
97
|
+
self.scroll = 1
|
|
98
|
+
|
|
99
|
+
def scrollBreak(self):
|
|
100
|
+
self.scroll += 1
|
|
101
|
+
|
|
102
|
+
def __init__(self, lb, sf, sr, cn, vm, bk, ch, sn, sc):
|
|
103
|
+
self.library = int(lb)
|
|
104
|
+
self.shelf = int(sf)
|
|
105
|
+
self.series = int(sr)
|
|
106
|
+
self.collection = int(cn)
|
|
107
|
+
self.volume = int(vm)
|
|
108
|
+
self.book = int(bk)
|
|
109
|
+
self.chapter = int(ch)
|
|
110
|
+
self.section = int(sn)
|
|
111
|
+
self.scroll = int(sc)
|
|
112
|
+
|
|
113
|
+
def copy_coordinate(self, other):
|
|
114
|
+
self.library = int(other.library)
|
|
115
|
+
self.shelf = int(other.shelf)
|
|
116
|
+
self.series = int(other.series)
|
|
117
|
+
self.collection = int(other.collection)
|
|
118
|
+
self.volume = int(other.volume)
|
|
119
|
+
self.book = int(other.book)
|
|
120
|
+
self.chapter = int(other.chapter)
|
|
121
|
+
self.section = int(other.section)
|
|
122
|
+
self.scroll = int(other.scroll)
|
|
123
|
+
|
|
124
|
+
def __str__(self) -> str:
|
|
125
|
+
return f"{self.library}.{self.shelf}.{self.series}/" \
|
|
126
|
+
f"{self.collection}.{self.volume}.{self.book}/" \
|
|
127
|
+
f"{self.chapter}.{self.section}.{self.scroll}"
|
|
128
|
+
|
|
129
|
+
def urlencoded(self) -> str:
|
|
130
|
+
return f"{self.library}.{self.shelf}.{self.series};" \
|
|
131
|
+
f"{self.collection}.{self.volume}.{self.book};" \
|
|
132
|
+
f"{self.chapter}.{self.section}.{self.scroll}"
|
|
133
|
+
|
|
134
|
+
def __eq__(self, other):
|
|
135
|
+
return isinstance(self, Coordinate) and isinstance(other, Coordinate) and self.library == other.library and self.shelf == other.shelf and self.series == other.series and self.collection == other.collection and self.volume == other.volume and self.book == other.book and self.chapter == other.chapter and self.section == other.section and self.scroll == other.scroll
|
|
136
|
+
|
|
137
|
+
def __lt__(self, other):
|
|
138
|
+
if not isinstance(other, Coordinate):
|
|
139
|
+
return NotImplemented
|
|
140
|
+
return self.as_tuple() < other.as_tuple()
|
|
141
|
+
|
|
142
|
+
def as_tuple(self) -> tuple:
|
|
143
|
+
return (
|
|
144
|
+
self.library, self.shelf, self.series,
|
|
145
|
+
self.collection, self.volume, self.book,
|
|
146
|
+
self.chapter, self.section, self.scroll
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def __hash__(self):
|
|
150
|
+
return hash((
|
|
151
|
+
self.library, self.shelf, self.series,
|
|
152
|
+
self.collection, self.volume, self.book,
|
|
153
|
+
self.chapter, self.section, self.scroll
|
|
154
|
+
))
|
phext/phext.py
ADDED
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import xxhash
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
from phext.coordinate import Coordinate
|
|
8
|
+
from phext.positionedScroll import PositionedScroll
|
|
9
|
+
from phext.range import Range
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class SubspaceBeacon:
|
|
13
|
+
start: int
|
|
14
|
+
end: int
|
|
15
|
+
best: Coordinate
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class Phext:
|
|
19
|
+
location: Coordinate
|
|
20
|
+
|
|
21
|
+
LIBRARY_BREAK = "\x01"
|
|
22
|
+
STX_BREAK = "\x02"
|
|
23
|
+
ETX_BREAK = "\x03"
|
|
24
|
+
MORE_COWBELL = "\x07"
|
|
25
|
+
SHELF_BREAK = "\x1F"
|
|
26
|
+
SERIES_BREAK = "\x1E"
|
|
27
|
+
COLLECTION_BREAK = "\x1D"
|
|
28
|
+
VOLUME_BREAK = "\x1C"
|
|
29
|
+
BOOK_BREAK = "\x1A"
|
|
30
|
+
CHAPTER_BREAK = "\x19"
|
|
31
|
+
SECTION_BREAK = "\x18"
|
|
32
|
+
SCROLL_BREAK = "\x17"
|
|
33
|
+
LINE_BREAK = "\x0a"
|
|
34
|
+
|
|
35
|
+
def defaultCoordinate(self) -> Coordinate:
|
|
36
|
+
return Coordinate(1,1,1, 1,1,1, 1,1,1)
|
|
37
|
+
|
|
38
|
+
def __init__(self):
|
|
39
|
+
location = self.defaultCoordinate()
|
|
40
|
+
|
|
41
|
+
def fetch(self, buffer:str, coord:Coordinate) -> dict[Coordinate, str]:
|
|
42
|
+
stack = self.phokenize(buffer)
|
|
43
|
+
for ps in stack:
|
|
44
|
+
if ps.coord == coord:
|
|
45
|
+
return ps.text
|
|
46
|
+
return ""
|
|
47
|
+
|
|
48
|
+
def isPhextBreak(self, byte:str):
|
|
49
|
+
return byte == self.LINE_BREAK or \
|
|
50
|
+
byte == self.SCROLL_BREAK or \
|
|
51
|
+
byte == self.SECTION_BREAK or \
|
|
52
|
+
byte == self.CHAPTER_BREAK or \
|
|
53
|
+
byte == self.BOOK_BREAK or \
|
|
54
|
+
byte == self.VOLUME_BREAK or \
|
|
55
|
+
byte == self.COLLECTION_BREAK or \
|
|
56
|
+
byte == self.SERIES_BREAK or \
|
|
57
|
+
byte == self.SHELF_BREAK or \
|
|
58
|
+
byte == self.LIBRARY_BREAK
|
|
59
|
+
|
|
60
|
+
def phokenize(self, buffer:str) -> List[PositionedScroll]:
|
|
61
|
+
result = []
|
|
62
|
+
location = self.defaultCoordinate()
|
|
63
|
+
next = self.defaultCoordinate()
|
|
64
|
+
temp = ""
|
|
65
|
+
# decoded = buffer.decode("utf-8")
|
|
66
|
+
for ch in buffer:
|
|
67
|
+
dimension_break = False
|
|
68
|
+
if ch == self.LIBRARY_BREAK:
|
|
69
|
+
next.libraryBreak()
|
|
70
|
+
dimension_break = True
|
|
71
|
+
if ch == self.SHELF_BREAK:
|
|
72
|
+
next.shelfBreak()
|
|
73
|
+
dimension_break = True
|
|
74
|
+
if ch == self.SERIES_BREAK:
|
|
75
|
+
next.seriesBreak()
|
|
76
|
+
dimension_break = True
|
|
77
|
+
if ch == self.COLLECTION_BREAK:
|
|
78
|
+
next.collectionBreak()
|
|
79
|
+
dimension_break = True
|
|
80
|
+
if ch == self.VOLUME_BREAK:
|
|
81
|
+
next.volumeBreak()
|
|
82
|
+
dimension_break = True
|
|
83
|
+
if ch == self.BOOK_BREAK:
|
|
84
|
+
next.bookBreak()
|
|
85
|
+
dimension_break = True
|
|
86
|
+
if ch == self.CHAPTER_BREAK:
|
|
87
|
+
next.chapterBreak()
|
|
88
|
+
dimension_break = True
|
|
89
|
+
if ch == self.SECTION_BREAK:
|
|
90
|
+
next.sectionBreak()
|
|
91
|
+
dimension_break = True
|
|
92
|
+
if ch == self.SCROLL_BREAK:
|
|
93
|
+
next.scrollBreak()
|
|
94
|
+
dimension_break = True
|
|
95
|
+
|
|
96
|
+
if dimension_break == True:
|
|
97
|
+
if len(temp) > 0:
|
|
98
|
+
item = PositionedScroll(location, temp)
|
|
99
|
+
result.append(item)
|
|
100
|
+
temp = ""
|
|
101
|
+
else:
|
|
102
|
+
temp += str(ch)
|
|
103
|
+
location = copy.deepcopy(next)
|
|
104
|
+
|
|
105
|
+
if len(temp) > 0:
|
|
106
|
+
item = PositionedScroll(location, temp)
|
|
107
|
+
result.append(item)
|
|
108
|
+
|
|
109
|
+
return result
|
|
110
|
+
|
|
111
|
+
def append_scroll(self, entry: PositionedScroll, location: Coordinate) -> PositionedScroll:
|
|
112
|
+
output = ""
|
|
113
|
+
compare = entry.coord
|
|
114
|
+
while location < entry.coord:
|
|
115
|
+
if location.library < compare.library:
|
|
116
|
+
output += Phext.LIBRARY_BREAK
|
|
117
|
+
location.libraryBreak()
|
|
118
|
+
continue
|
|
119
|
+
if location.shelf < compare.shelf:
|
|
120
|
+
output += Phext.SHELF_BREAK
|
|
121
|
+
location.shelfBreak()
|
|
122
|
+
continue
|
|
123
|
+
if location.series < compare.series:
|
|
124
|
+
output += Phext.SERIES_BREAK
|
|
125
|
+
location.seriesBreak()
|
|
126
|
+
continue
|
|
127
|
+
if location.collection < compare.collection:
|
|
128
|
+
output += Phext.COLLECTION_BREAK
|
|
129
|
+
location.collectionBreak()
|
|
130
|
+
continue
|
|
131
|
+
if location.volume < compare.volume:
|
|
132
|
+
output += Phext.VOLUME_BREAK
|
|
133
|
+
location.volumeBreak()
|
|
134
|
+
continue
|
|
135
|
+
if location.book < compare.book:
|
|
136
|
+
output += Phext.BOOK_BREAK
|
|
137
|
+
location.bookBreak()
|
|
138
|
+
continue
|
|
139
|
+
if location.chapter < compare.chapter:
|
|
140
|
+
output += Phext.CHAPTER_BREAK
|
|
141
|
+
location.chapterBreak()
|
|
142
|
+
continue
|
|
143
|
+
if location.section < compare.section:
|
|
144
|
+
output += Phext.SECTION_BREAK
|
|
145
|
+
location.sectionBreak()
|
|
146
|
+
continue
|
|
147
|
+
if location.scroll < compare.scroll:
|
|
148
|
+
output += Phext.SCROLL_BREAK
|
|
149
|
+
location.scrollBreak()
|
|
150
|
+
continue
|
|
151
|
+
if len(entry.text) > 0:
|
|
152
|
+
output += entry.text
|
|
153
|
+
result = PositionedScroll(location, output)
|
|
154
|
+
return result
|
|
155
|
+
|
|
156
|
+
def dephokenize(self, stack: list[PositionedScroll]) -> str:
|
|
157
|
+
result = ""
|
|
158
|
+
location = self.defaultCoordinate()
|
|
159
|
+
for ps in stack:
|
|
160
|
+
next = self.append_scroll(ps, location)
|
|
161
|
+
result += next.text
|
|
162
|
+
location = next.coord
|
|
163
|
+
return result
|
|
164
|
+
|
|
165
|
+
def normalize(self, buffer:str) -> str:
|
|
166
|
+
arr = self.phokenize(buffer)
|
|
167
|
+
return self.dephokenize(arr)
|
|
168
|
+
|
|
169
|
+
def update(self, buffer:str, coord:Coordinate, scroll:str, overwrite:bool) -> str:
|
|
170
|
+
items = self.phokenize(buffer)
|
|
171
|
+
result = []
|
|
172
|
+
next = PositionedScroll(coord, scroll)
|
|
173
|
+
appended = False
|
|
174
|
+
for ps in items:
|
|
175
|
+
if (ps.coord > coord) and (appended == False):
|
|
176
|
+
result.append(next)
|
|
177
|
+
appended = True
|
|
178
|
+
if (ps.coord == coord) and (appended == False):
|
|
179
|
+
if overwrite == False:
|
|
180
|
+
next.text = ps.text + next.text
|
|
181
|
+
result.append(next)
|
|
182
|
+
appended = True
|
|
183
|
+
continue
|
|
184
|
+
result.append(ps)
|
|
185
|
+
if appended == False:
|
|
186
|
+
result.append(next)
|
|
187
|
+
appended = True
|
|
188
|
+
|
|
189
|
+
serialized = self.dephokenize(result)
|
|
190
|
+
return serialized
|
|
191
|
+
|
|
192
|
+
def insert(self, buffer:str, coord:Coordinate, scroll:str) -> str:
|
|
193
|
+
return self.update(buffer, coord, scroll, False)
|
|
194
|
+
|
|
195
|
+
def replace(self, buffer:str, coord:Coordinate, scroll:str) -> str:
|
|
196
|
+
return self.update(buffer, coord, scroll, True)
|
|
197
|
+
|
|
198
|
+
def remove(self, buffer:str, coord:Coordinate) -> str:
|
|
199
|
+
intermediate = self.update(buffer, coord, "", True)
|
|
200
|
+
return self.normalize(intermediate)
|
|
201
|
+
|
|
202
|
+
def range_replace(self, buffer:str, range:Range, text:str) -> str:
|
|
203
|
+
stack = self.phokenize(buffer)
|
|
204
|
+
result = []
|
|
205
|
+
appended = False
|
|
206
|
+
for ps in stack:
|
|
207
|
+
if ps.coord < range.start and len(ps.text) > 0:
|
|
208
|
+
result.append(ps)
|
|
209
|
+
if ps.coord >= range.start and ps.coord <= range.end:
|
|
210
|
+
if appended == False and len(text) > 0:
|
|
211
|
+
next = PositionedScroll(ps.coord, text)
|
|
212
|
+
result.append(next)
|
|
213
|
+
appended = True
|
|
214
|
+
if ps.coord > range.end and len(ps.text) > 0:
|
|
215
|
+
result.append(ps)
|
|
216
|
+
return self.dephokenize(result)
|
|
217
|
+
|
|
218
|
+
def next_scroll(self, buffer:str, coord:Coordinate) -> List[PositionedScroll]:
|
|
219
|
+
stack = self.phokenize(buffer)
|
|
220
|
+
found = False
|
|
221
|
+
result = []
|
|
222
|
+
for ps in stack:
|
|
223
|
+
if found == True:
|
|
224
|
+
result.append(ps)
|
|
225
|
+
return result
|
|
226
|
+
if ps.coord >= coord:
|
|
227
|
+
result.append(ps)
|
|
228
|
+
found = True
|
|
229
|
+
return result
|
|
230
|
+
|
|
231
|
+
def get_subspace_coordinates(self, buffer:str, target:Coordinate):
|
|
232
|
+
walker = self.defaultCoordinate()
|
|
233
|
+
best = self.defaultCoordinate()
|
|
234
|
+
subspace_index = 0
|
|
235
|
+
start = 0
|
|
236
|
+
end = 0
|
|
237
|
+
stage = 0
|
|
238
|
+
max = len(buffer)
|
|
239
|
+
|
|
240
|
+
while subspace_index < max:
|
|
241
|
+
next = buffer[subspace_index]
|
|
242
|
+
compare = next
|
|
243
|
+
|
|
244
|
+
if stage == 0:
|
|
245
|
+
if walker == target:
|
|
246
|
+
stage = 1
|
|
247
|
+
start = subspace_index
|
|
248
|
+
best = copy.deepcopy(walker)
|
|
249
|
+
if walker < target:
|
|
250
|
+
best = copy.deepcopy(walker)
|
|
251
|
+
if stage < 2 and walker > target:
|
|
252
|
+
if stage == 0:
|
|
253
|
+
start = subspace_index - 1
|
|
254
|
+
end = subspace_index - 1
|
|
255
|
+
stage = 2
|
|
256
|
+
|
|
257
|
+
if self.isPhextBreak(next):
|
|
258
|
+
if compare == self.SCROLL_BREAK:
|
|
259
|
+
walker.scrollBreak()
|
|
260
|
+
if compare == self.SECTION_BREAK:
|
|
261
|
+
walker.sectionBreak()
|
|
262
|
+
if compare == self.CHAPTER_BREAK:
|
|
263
|
+
walker.chapterBreak()
|
|
264
|
+
if compare == self.BOOK_BREAK:
|
|
265
|
+
walker.bookBreak()
|
|
266
|
+
if compare == self.VOLUME_BREAK:
|
|
267
|
+
walker.volumeBreak()
|
|
268
|
+
if compare == self.COLLECTION_BREAK:
|
|
269
|
+
walker.collectionBreak()
|
|
270
|
+
if compare == self.SERIES_BREAK:
|
|
271
|
+
walker.seriesBreak()
|
|
272
|
+
if compare == self.SHELF_BREAK:
|
|
273
|
+
walker.shelfBreak()
|
|
274
|
+
if compare == self.LIBRARY_BREAK:
|
|
275
|
+
walker.libraryBreak()
|
|
276
|
+
|
|
277
|
+
if stage < 2 and walker > target:
|
|
278
|
+
end = subspace_index
|
|
279
|
+
stage = 2
|
|
280
|
+
|
|
281
|
+
subspace_index += 1
|
|
282
|
+
|
|
283
|
+
if stage == 1 and walker == target:
|
|
284
|
+
end = max
|
|
285
|
+
stage = 2
|
|
286
|
+
|
|
287
|
+
if stage == 0:
|
|
288
|
+
start = max
|
|
289
|
+
end = max
|
|
290
|
+
|
|
291
|
+
result = SubspaceBeacon(start, end, best)
|
|
292
|
+
return result
|
|
293
|
+
|
|
294
|
+
def merge(self, left: str, right:str) -> str:
|
|
295
|
+
tl = self.phokenize(left)
|
|
296
|
+
tr = self.phokenize(right)
|
|
297
|
+
tli = 0
|
|
298
|
+
tri = 0
|
|
299
|
+
maxtl = len(tl)
|
|
300
|
+
maxtr = len(tr)
|
|
301
|
+
result = ""
|
|
302
|
+
coord = self.defaultCoordinate()
|
|
303
|
+
|
|
304
|
+
while True:
|
|
305
|
+
have_left = tli < maxtl
|
|
306
|
+
have_right = tri < maxtr
|
|
307
|
+
pick_left = have_left and (have_right == False or tl[tli].coord <= tr[tri].coord)
|
|
308
|
+
pick_right = have_right and (have_left == False or tr[tri].coord <= tl[tli].coord)
|
|
309
|
+
|
|
310
|
+
if pick_left:
|
|
311
|
+
next = self.append_scroll(tl[tli], coord)
|
|
312
|
+
result += next.text
|
|
313
|
+
coord = next.coord
|
|
314
|
+
tli += 1
|
|
315
|
+
if pick_right:
|
|
316
|
+
next = self.append_scroll(tr[tri], coord)
|
|
317
|
+
result += next.text
|
|
318
|
+
coord = next.coord
|
|
319
|
+
tri += 1
|
|
320
|
+
|
|
321
|
+
if pick_left == False and pick_right == False:
|
|
322
|
+
break
|
|
323
|
+
|
|
324
|
+
return result
|
|
325
|
+
|
|
326
|
+
def subtract(self, left:str, right:str) -> str:
|
|
327
|
+
pl = self.phokenize(left)
|
|
328
|
+
pr = self.phokenize(right)
|
|
329
|
+
result = ""
|
|
330
|
+
pri = 0
|
|
331
|
+
max = len(pr)
|
|
332
|
+
coord = self.defaultCoordinate()
|
|
333
|
+
for item in pl:
|
|
334
|
+
do_append = pri == max
|
|
335
|
+
if pri < max:
|
|
336
|
+
compare = pr[pri]
|
|
337
|
+
if item.coord < compare.coord:
|
|
338
|
+
do_append = True
|
|
339
|
+
elif item.coord == compare.coord:
|
|
340
|
+
pri += 1
|
|
341
|
+
if do_append:
|
|
342
|
+
next = self.append_scroll(item, coord)
|
|
343
|
+
result += next.text
|
|
344
|
+
coord = next.coord
|
|
345
|
+
return result
|
|
346
|
+
|
|
347
|
+
def expand(self, buffer:str) -> str:
|
|
348
|
+
result = ""
|
|
349
|
+
for char in buffer:
|
|
350
|
+
if char == self.LINE_BREAK:
|
|
351
|
+
result += self.SCROLL_BREAK
|
|
352
|
+
elif char == self.SCROLL_BREAK:
|
|
353
|
+
result += self.SECTION_BREAK
|
|
354
|
+
elif char == self.SECTION_BREAK:
|
|
355
|
+
result += self.CHAPTER_BREAK
|
|
356
|
+
elif char == self.CHAPTER_BREAK:
|
|
357
|
+
result += self.BOOK_BREAK
|
|
358
|
+
elif char == self.BOOK_BREAK:
|
|
359
|
+
result += self.VOLUME_BREAK
|
|
360
|
+
elif char == self.VOLUME_BREAK:
|
|
361
|
+
result += self.COLLECTION_BREAK
|
|
362
|
+
elif char == self.COLLECTION_BREAK:
|
|
363
|
+
result += self.SERIES_BREAK
|
|
364
|
+
elif char == self.SERIES_BREAK:
|
|
365
|
+
result += self.SHELF_BREAK
|
|
366
|
+
elif char == self.SHELF_BREAK:
|
|
367
|
+
result += self.LIBRARY_BREAK
|
|
368
|
+
else:
|
|
369
|
+
result += char
|
|
370
|
+
return result
|
|
371
|
+
|
|
372
|
+
def contract(self, buffer:str) -> str:
|
|
373
|
+
result = ""
|
|
374
|
+
for char in buffer:
|
|
375
|
+
if char == self.LIBRARY_BREAK:
|
|
376
|
+
result += self.SHELF_BREAK
|
|
377
|
+
elif char == self.SHELF_BREAK:
|
|
378
|
+
result += self.SERIES_BREAK
|
|
379
|
+
elif char == self.SERIES_BREAK:
|
|
380
|
+
result += self.COLLECTION_BREAK
|
|
381
|
+
elif char == self.COLLECTION_BREAK:
|
|
382
|
+
result += self.VOLUME_BREAK
|
|
383
|
+
elif char == self.VOLUME_BREAK:
|
|
384
|
+
result += self.BOOK_BREAK
|
|
385
|
+
elif char == self.BOOK_BREAK:
|
|
386
|
+
result += self.CHAPTER_BREAK
|
|
387
|
+
elif char == self.CHAPTER_BREAK:
|
|
388
|
+
result += self.SECTION_BREAK
|
|
389
|
+
elif char == self.SECTION_BREAK:
|
|
390
|
+
result += self.SCROLL_BREAK
|
|
391
|
+
elif char == self.SCROLL_BREAK:
|
|
392
|
+
result += self.LINE_BREAK
|
|
393
|
+
else:
|
|
394
|
+
result += char
|
|
395
|
+
return result
|
|
396
|
+
|
|
397
|
+
def create_summary(self, buffer:str) -> str:
|
|
398
|
+
limit = 32
|
|
399
|
+
max = len(buffer)
|
|
400
|
+
if max < 32:
|
|
401
|
+
limit = max
|
|
402
|
+
summary = ""
|
|
403
|
+
for char in buffer:
|
|
404
|
+
if self.isPhextBreak(char):
|
|
405
|
+
break
|
|
406
|
+
summary += char
|
|
407
|
+
if len(summary) < len(buffer):
|
|
408
|
+
summary += "..."
|
|
409
|
+
return summary
|
|
410
|
+
|
|
411
|
+
def navmap(self, urlbase:str, buffer:str) -> str:
|
|
412
|
+
stack = self.phokenize(buffer)
|
|
413
|
+
result = ""
|
|
414
|
+
max = len(stack)
|
|
415
|
+
if max > 0:
|
|
416
|
+
result += "<ul>\n"
|
|
417
|
+
for ps in stack:
|
|
418
|
+
urlcoord = ps.coord.urlencoded()
|
|
419
|
+
summary = self.create_summary(ps.text)
|
|
420
|
+
result += f"<li><a href=\"{urlbase}{urlcoord}\">{ps.coord} {summary}</a></li>\n"
|
|
421
|
+
if max > 0:
|
|
422
|
+
result += "</ul>\n"
|
|
423
|
+
return result
|
|
424
|
+
|
|
425
|
+
def textmap(self, buffer:str) -> str:
|
|
426
|
+
stack = self.phokenize(buffer)
|
|
427
|
+
result = ""
|
|
428
|
+
for ps in stack:
|
|
429
|
+
summary = self.create_summary(ps.text)
|
|
430
|
+
result += f"* {ps.coord}: {summary}\n"
|
|
431
|
+
return result
|
|
432
|
+
|
|
433
|
+
def checksum(self, buffer:str) -> str:
|
|
434
|
+
hash = xxhash.xxh3_128(buffer).hexdigest()
|
|
435
|
+
return hash
|
|
436
|
+
|
|
437
|
+
def manifest(self, buffer:str) -> str:
|
|
438
|
+
stack = self.phokenize(buffer)
|
|
439
|
+
for ps in stack:
|
|
440
|
+
ps.text = self.checksum(ps.text)
|
|
441
|
+
result = self.dephokenize(stack)
|
|
442
|
+
return result
|
|
443
|
+
|
|
444
|
+
def soundex_v1(self, buffer:str) -> str:
|
|
445
|
+
stack = self.phokenize(buffer)
|
|
446
|
+
|
|
447
|
+
for ps in stack:
|
|
448
|
+
ps.text = self.soundex_internal(ps.text)
|
|
449
|
+
|
|
450
|
+
return self.dephokenize(stack)
|
|
451
|
+
|
|
452
|
+
def soundex_internal(self, buffer:str) -> str:
|
|
453
|
+
letter1 = "bpfv"
|
|
454
|
+
letter2 = "cskgjqxz"
|
|
455
|
+
letter3 = "dt"
|
|
456
|
+
letter4 = "l"
|
|
457
|
+
letter5 = "mn"
|
|
458
|
+
letter6 = "r"
|
|
459
|
+
|
|
460
|
+
value = 1 # 1-100
|
|
461
|
+
for byte in buffer:
|
|
462
|
+
if byte in letter1:
|
|
463
|
+
value += 1
|
|
464
|
+
if byte in letter2:
|
|
465
|
+
value += 2
|
|
466
|
+
if byte in letter3:
|
|
467
|
+
value += 3
|
|
468
|
+
if byte in letter4:
|
|
469
|
+
value += 4
|
|
470
|
+
if byte in letter5:
|
|
471
|
+
value += 5
|
|
472
|
+
if byte in letter6:
|
|
473
|
+
value += 6
|
|
474
|
+
return str(value % 99)
|
|
475
|
+
|
|
476
|
+
def explode(self, buffer:str) -> dict[Coordinate, str]:
|
|
477
|
+
parts = self.phokenize(buffer)
|
|
478
|
+
hash: dict[Coordinate, str] = {}
|
|
479
|
+
for ps in parts:
|
|
480
|
+
hash[ps.coord] = ps.text
|
|
481
|
+
return hash
|
|
482
|
+
|
|
483
|
+
def implode(self, hash: dict[Coordinate, str]) -> str:
|
|
484
|
+
parts = []
|
|
485
|
+
for coord in hash.keys():
|
|
486
|
+
parts.append(PositionedScroll(coord, hash[coord]))
|
|
487
|
+
return self.dephokenize(parts)
|
|
488
|
+
|
|
489
|
+
def index_phokens(self, buffer:str) -> list[PositionedScroll]:
|
|
490
|
+
stack = self.phokenize(buffer)
|
|
491
|
+
offset = 0
|
|
492
|
+
coord = self.defaultCoordinate()
|
|
493
|
+
output = []
|
|
494
|
+
for ps in stack:
|
|
495
|
+
reference = ps.coord
|
|
496
|
+
while coord.library < reference.library:
|
|
497
|
+
coord.libraryBreak()
|
|
498
|
+
offset += 1
|
|
499
|
+
while coord.shelf < reference.shelf:
|
|
500
|
+
coord.shelfBreak()
|
|
501
|
+
offset += 1
|
|
502
|
+
while coord.series < reference.series:
|
|
503
|
+
coord.seriesBreak()
|
|
504
|
+
offset += 1
|
|
505
|
+
while coord.collection < reference.collection:
|
|
506
|
+
coord.collectionBreak()
|
|
507
|
+
offset += 1
|
|
508
|
+
while coord.volume < reference.volume:
|
|
509
|
+
coord.volumeBreak()
|
|
510
|
+
offset += 1
|
|
511
|
+
while coord.book < reference.book:
|
|
512
|
+
coord.bookBreak()
|
|
513
|
+
offset += 1
|
|
514
|
+
while coord.chapter < reference.chapter:
|
|
515
|
+
coord.chapterBreak()
|
|
516
|
+
offset += 1
|
|
517
|
+
while coord.section < reference.section:
|
|
518
|
+
coord.sectionBreak()
|
|
519
|
+
offset += 1
|
|
520
|
+
while coord.scroll < reference.scroll:
|
|
521
|
+
coord.scrollBreak()
|
|
522
|
+
offset += 1
|
|
523
|
+
text = str(offset)
|
|
524
|
+
output.append(PositionedScroll(copy.deepcopy(coord), text))
|
|
525
|
+
offset += len(ps.text)
|
|
526
|
+
|
|
527
|
+
return output
|
|
528
|
+
|
|
529
|
+
def index(self, buffer:str) -> str:
|
|
530
|
+
output = self.index_phokens(buffer)
|
|
531
|
+
return self.dephokenize(output)
|
|
532
|
+
|
|
533
|
+
def offset(self, buffer:str, coord:Coordinate) -> int:
|
|
534
|
+
output = self.index_phokens(buffer)
|
|
535
|
+
best = self.defaultCoordinate()
|
|
536
|
+
fetch_coord = coord
|
|
537
|
+
matched = False
|
|
538
|
+
for ps in output:
|
|
539
|
+
if ps.coord <= coord:
|
|
540
|
+
best = copy.deepcopy(ps.coord)
|
|
541
|
+
if ps.coord == coord:
|
|
542
|
+
matched = True
|
|
543
|
+
if matched == False:
|
|
544
|
+
fetch_coord = best
|
|
545
|
+
index = self.dephokenize(output)
|
|
546
|
+
return int(self.fetch(index, fetch_coord))
|
phext/range.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: phext
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Pythonic implementation of phext
|
|
5
|
+
Project-URL: Homepage, https://phext.io
|
|
6
|
+
Project-URL: Repository, https://github.com/wbic16/phext-py
|
|
7
|
+
Project-URL: Issues, https://github.com/wbic16/phext-py/issues
|
|
8
|
+
Author-email: Will Bickford <wbic16@gmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Requires-Dist: xxhash
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
21
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# Phext
|
|
25
|
+
|
|
26
|
+
Welcome to the Pythonic port of phext.
|
|
27
|
+
|
|
28
|
+
This port is a downstream artifact that tracks the Rust-based implementation of Phext. See https://github.com/wbic16/phext-rs for more details.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
phext/__init__.py,sha256=5mDtfU2t04ATmg92S5B-scUcdjCy1ZBq620y3GmpdtQ,21
|
|
2
|
+
phext/coordinate.py,sha256=dAaLTJUF9znpsm_nnVYiLV6dfQE769JhcKlxvQfazms,4821
|
|
3
|
+
phext/phext.py,sha256=P9er7vRegrTjhCMWcIitwqq704cIQQuUXtFPAognqEg,17171
|
|
4
|
+
phext/positionedScroll.py,sha256=b-p4wffIY_E_DDwRJxzOMIymlK1-2nTQVVVD7CADPGw,249
|
|
5
|
+
phext/range.py,sha256=tV3Zte0jxvA1FrTHFwRkjsRgEijyhm0QgUM1x3SvesA,142
|
|
6
|
+
phext-0.3.0.dist-info/METADATA,sha256=gvpUGRm0doT7VvCVUGDAfRo-JHetNXWl6frcHlLzzDM,1019
|
|
7
|
+
phext-0.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
8
|
+
phext-0.3.0.dist-info/RECORD,,
|