175 lines
5.6 KiB
Python
175 lines
5.6 KiB
Python
# cython: auto_pickle=False
|
|
|
|
r"""
|
|
Implements a buffer with insertion points. When you know you need to
|
|
"get back" to a place and write more later, simply call insertion_point()
|
|
at that spot and get a new StringIOTree object that is "left behind".
|
|
|
|
EXAMPLE:
|
|
|
|
>>> a = StringIOTree()
|
|
>>> _= a.write('first\n')
|
|
>>> b = a.insertion_point()
|
|
>>> _= a.write('third\n')
|
|
>>> _= b.write('second\n')
|
|
>>> a.getvalue().split()
|
|
['first', 'second', 'third']
|
|
|
|
>>> c = b.insertion_point()
|
|
>>> d = c.insertion_point()
|
|
>>> _= d.write('alpha\n')
|
|
>>> _= b.write('gamma\n')
|
|
>>> _= c.write('beta\n')
|
|
>>> b.getvalue().split()
|
|
['second', 'alpha', 'beta', 'gamma']
|
|
|
|
>>> try: from cStringIO import StringIO
|
|
... except ImportError: from io import StringIO
|
|
|
|
>>> i = StringIOTree()
|
|
>>> d.insert(i)
|
|
>>> _= i.write('inserted\n')
|
|
>>> out = StringIO()
|
|
>>> a.copyto(out)
|
|
>>> out.getvalue().split()
|
|
['first', 'second', 'alpha', 'inserted', 'beta', 'gamma', 'third']
|
|
"""
|
|
|
|
from __future__ import absolute_import #, unicode_literals
|
|
|
|
try:
|
|
# Prefer cStringIO since io.StringIO() does not support writing 'str' in Py2.
|
|
from cStringIO import StringIO
|
|
except ImportError:
|
|
from io import StringIO
|
|
|
|
|
|
class StringIOTree(object):
|
|
"""
|
|
See module docs.
|
|
"""
|
|
|
|
def __init__(self, stream=None):
|
|
self.prepended_children = []
|
|
if stream is None:
|
|
stream = StringIO()
|
|
self.stream = stream
|
|
self.write = stream.write
|
|
self.markers = []
|
|
|
|
def empty(self):
|
|
if self.stream.tell():
|
|
return False
|
|
return all([child.empty() for child in self.prepended_children]) if self.prepended_children else True
|
|
|
|
def getvalue(self):
|
|
content = []
|
|
self._collect_in(content)
|
|
return "".join(content)
|
|
|
|
def _collect_in(self, target_list):
|
|
for x in self.prepended_children:
|
|
x._collect_in(target_list)
|
|
stream_content = self.stream.getvalue()
|
|
if stream_content:
|
|
target_list.append(stream_content)
|
|
|
|
def copyto(self, target):
|
|
"""Potentially cheaper than getvalue as no string concatenation
|
|
needs to happen."""
|
|
for child in self.prepended_children:
|
|
child.copyto(target)
|
|
stream_content = self.stream.getvalue()
|
|
if stream_content:
|
|
target.write(stream_content)
|
|
|
|
def commit(self):
|
|
# Save what we have written until now so that the buffer
|
|
# itself is empty -- this makes it ready for insertion
|
|
if self.stream.tell():
|
|
self.prepended_children.append(StringIOTree(self.stream))
|
|
self.prepended_children[-1].markers = self.markers
|
|
self.markers = []
|
|
self.stream = StringIO()
|
|
self.write = self.stream.write
|
|
|
|
def reset(self):
|
|
self.prepended_children = []
|
|
self.markers = []
|
|
self.stream = StringIO()
|
|
self.write = self.stream.write
|
|
|
|
def insert(self, iotree):
|
|
"""
|
|
Insert a StringIOTree (and all of its contents) at this location.
|
|
Further writing to self appears after what is inserted.
|
|
"""
|
|
self.commit()
|
|
self.prepended_children.append(iotree)
|
|
|
|
def insertion_point(self):
|
|
"""
|
|
Returns a new StringIOTree, which is left behind at the current position
|
|
(it what is written to the result will appear right before whatever is
|
|
next written to self).
|
|
|
|
Calling getvalue() or copyto() on the result will only return the
|
|
contents written to it.
|
|
"""
|
|
# Save what we have written until now
|
|
# This is so that getvalue on the result doesn't include it.
|
|
self.commit()
|
|
# Construct the new forked object to return
|
|
other = StringIOTree()
|
|
self.prepended_children.append(other)
|
|
return other
|
|
|
|
def allmarkers(self):
|
|
children = self.prepended_children
|
|
return [m for c in children for m in c.allmarkers()] + self.markers
|
|
|
|
"""
|
|
# Print the result of allmarkers in a nice human-readable form. Use it only for debugging.
|
|
# Prints e.g.
|
|
# /path/to/source.pyx:
|
|
# cython line 2 maps to 3299-3343
|
|
# cython line 4 maps to 2236-2245 2306 3188-3201
|
|
# /path/to/othersource.pyx:
|
|
# cython line 3 maps to 1234-1270
|
|
# ...
|
|
# Note: In the example above, 3343 maps to line 2, 3344 does not.
|
|
def print_hr_allmarkers(self):
|
|
from collections import defaultdict
|
|
markers = self.allmarkers()
|
|
totmap = defaultdict(lambda: defaultdict(list))
|
|
for c_lineno, (cython_desc, cython_lineno) in enumerate(markers):
|
|
if cython_lineno > 0 and cython_desc.filename is not None:
|
|
totmap[cython_desc.filename][cython_lineno].append(c_lineno + 1)
|
|
reprstr = ""
|
|
if totmap == 0:
|
|
reprstr += "allmarkers is empty\n"
|
|
try:
|
|
sorted(totmap.items())
|
|
except:
|
|
print(totmap)
|
|
print(totmap.items())
|
|
for cython_path, filemap in sorted(totmap.items()):
|
|
reprstr += cython_path + ":\n"
|
|
for cython_lineno, c_linenos in sorted(filemap.items()):
|
|
reprstr += "\tcython line " + str(cython_lineno) + " maps to "
|
|
i = 0
|
|
while i < len(c_linenos):
|
|
reprstr += str(c_linenos[i])
|
|
flag = False
|
|
while i+1 < len(c_linenos) and c_linenos[i+1] == c_linenos[i]+1:
|
|
i += 1
|
|
flag = True
|
|
if flag:
|
|
reprstr += "-" + str(c_linenos[i]) + " "
|
|
i += 1
|
|
reprstr += "\n"
|
|
|
|
import sys
|
|
sys.stdout.write(reprstr)
|
|
"""
|