comparison workspace.py @ 4:7a83e82e65a6

Remove some deadwood.
author David Barts <n5jrn@me.com>
date Thu, 26 Dec 2019 20:04:04 -0800
parents 091c03f1b2e8
children
comparison
equal deleted inserted replaced
3:091c03f1b2e8 4:7a83e82e65a6
1 #!/usr/bin/env python3 1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*- 2 # -*- coding: utf-8 -*-
3
4 # Classes that implement a workspace for curly-quoting a text, and views
5 # into the same.
6 3
7 # I m p o r t s 4 # I m p o r t s
8 5
9 import os, sys 6 import os, sys
10 import io 7 import io
180 """ 177 """
181 Context manager: close on exit. 178 Context manager: close on exit.
182 """ 179 """
183 self.close() 180 self.close()
184 return False 181 return False
185
186 class Bounds(object):
187 """
188 A set of index bounds.
189 """
190 def __init__(self, start, stop):
191 if start > stop or start < 0 or stop < 0:
192 raise ValueError("invalid bounds")
193 self.start = int(start)
194 self.stop = int(stop)
195
196 @classmethod
197 def from_object(cls, obj):
198 if isinstance(obj, slice):
199 return self(slice.start, slice.stop)
200 return self(obj[0], obj[1])
201
202 def __lt__(self, other):
203 return self.start < other.start
204
205 def __le__(self, other):
206 return self.start <= other.start
207
208 def __eq__(self, other):
209 return self.start == other.start
210
211 def __ne__(self, other):
212 return self.start != other.start
213
214 def __gt__(self, other):
215 return self.start > other.start
216
217 def __ge__(self, other):
218 return self.start >= other.start
219
220 def __contains__(self, scalar):
221 return self.start <= scalar < self.stop
222
223 def __repr__(self):
224 return "{0}({1!r}, {2!r})".format(self.__class__.__name__, self.start, self.stop)
225
226 class Mapping(object):
227 """
228 Represents a mapping of a single view segment into an indexable
229 object.
230 """
231 def __init__(self, bounds, offset):
232 if not isinstance(bounds, Bounds):
233 raise TypeError("bounds must be a Bounds object")
234 if not isinstance(offset, int):
235 raise TypeError("offset must be an int")
236 self.bounds = bounds
237 self.offset = offset
238 self.delta = self.offset - self.bounds.start
239
240 def __repr__(self):
241 return "{0}({1!r}, {2!r})".format(self.__class__.__name__, self.bounds, self.offset)
242
243 class SegmentedView(object):
244 """
245 Implements a view on a subscriptable object. The view is composed of
246 zero or more segments of the source object. Has the same idiosyncratic
247 behavior for out-of-bounds indices that Workspace has (and for the
248 same reason). Mutating this object causes the parent object to also
249 be mutated.
250 """
251 def __init__(self, indexable, bounds):
252 self.indexable = indexable
253 self._mmap = [ Mapping(Bounds(0, 0), 0) ]
254 pos = 0
255 for r in sorted(bounds):
256 if pos is not None and r.start <= pos and r.stop > pos:
257 # merge ranges
258 self._mmap[-1].bounds.stop = r.stop
259 pos = r.stop
260 continue
261 opos = pos
262 pos += r.stop - r.start
263 self._mmap.append(Mapping(Bounds(opos, pos), r.start))
264 self._length = pos
265
266 def _mapped(self, index):
267 mmap_index = self._binsch(index)
268 if mmap_index is None:
269 raise IndexError("index {0} out of range".format(index))
270 return index + self._mmap[mmap_index].delta
271
272 def _binsch(self, index):
273 a = 0
274 z = len(self._mmap) - 1
275 while a <= z:
276 m = (a + z) // 2
277 if index in self._mmap[m].bounds:
278 return m
279 if index < self._mmap[m].bounds.start:
280 z = m - 1
281 else:
282 assert index >= self._mmap[m].bounds.stop
283 a = m + 1
284 return None
285
286 def __setitem__(self, key, value):
287 """
288 Direct access to replace a single character.
289 """
290 if not isinstance(key, int):
291 raise TypeError("__setitem__ only supports integers")
292 self.indexable[self._mapped(key)] = value
293
294 def __getitem__(self, key):
295 """
296 Direct access to a single character or range of characters.
297 """
298 # Trivial cases
299 if isinstance(key, int):
300 return self._get1(key)
301 if not isinstance(key, slice):
302 raise TypeError("expecting int or slice")
303 if key.step is not None:
304 raise ValueError("__getitem__ does not support steps in slices")
305
306 # Loop up the starting segment.
307 mi = self._binsch(key.start)
308 if mi is None:
309 return ""
310 m = self._mmap[mi]
311
312 # Horray! There's only one segment, so we can optimize.
313 if key.stop <= m.bounds.stop:
314 start = key.start + m.delta
315 stop = key.stop + m.delta
316 return self.indexable[start:stop]
317
318 # The most involved (multi-segment) case.
319 with io.StringIO() as buf:
320 for m in self._mmap[mi:]:
321 if m.bounds.start >= key.stop:
322 break
323 start = max(key.start, m.bounds.start) + m.delta
324 stop = min(key.stop, m.bounds.stop) + m.delta
325 buf.write(self.indexable[start:stop])
326 return buf.getvalue()
327
328 def __len__(self):
329 return self._length
330
331 def _get1(self, index):
332 try:
333 return self.indexable[self._mapped(index)]
334 except IndexError:
335 return ""
336
337 def getvalue(self):
338 return self[0:len(self)]