Skip to content

Commit

Permalink
Add source.Rewriter.
Browse files Browse the repository at this point in the history
whitequark committed Apr 24, 2015
1 parent 8b9a887 commit fd7209a
Showing 2 changed files with 88 additions and 0 deletions.
25 changes: 25 additions & 0 deletions examples/quot_to_dquot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import sys, re
from pyparser import source, lexer

buf = None
with open(sys.argv[1]) as f:
buf = source.Buffer(f.read(), f.name)

rewriter = source.Rewriter(buf)
in_quot = False
replace = { "'": '"', "'''": '"""' }
for token in lexer.Lexer(buf, (3, 4)):
source = token.loc.source()
if token.kind == 'strbegin' and source in replace.keys():
rewriter.replace(token.loc, replace[source])
in_quot = True
elif token.kind == 'strdata' and in_quot:
rewriter.replace(token.loc, re.sub(r'([^\\"]|)"', r'\1\\"', source))
elif token.kind == 'strend' and in_quot:
rewriter.replace(token.loc, replace[source])
in_quot = False

buf = rewriter.rewrite()

with open(sys.argv[1], 'w') as f:
f.write(buf.source)
63 changes: 63 additions & 0 deletions pyparser/source.py
Original file line number Diff line number Diff line change
@@ -169,3 +169,66 @@ def __ne__(self, other):
Inverse of :meth:`__eq__`.
"""
return not (self == other)

class RewriterConflict(Exception):
"""
An exception that is raised when two ranges supplied to a rewriter overlap.
:ivar first: (:class:`Range`) first overlapping range
:ivar second: (:class:`Range`) second overlapping range
"""

def __init__(self, first, second):
self.first, self.second = first, second
exception.__init__(self, "Ranges %s and %s overlap" % (repr(first), repr(second)))

class Rewriter:
"""
The :class:`Rewriter` class rewrites source code: performs bulk modification
guided by a list of ranges and code fragments replacing their original
content.
:ivar buffer: (:class:`Buffer`) buffer
"""

def __init__(self, buffer):
self.buffer = buffer
self.ranges = []

def replace(self, range, replacement):
"""Remove `range` and replace it with string `replacement`."""
self.ranges.append((range, replacement))

def remove(self, range):
"""Remove `range`."""
self.replace(self, range, "")

def insert_before(self, range, text):
"""Insert `text` before `range`."""
self.replace(self, range.begin(), "")

def insert_after(self, range, text):
"""Insert `text` after `range`."""
self.replace(self, range.end(), "")

def rewrite(self):
"""Return the rewritten source. May raise :class:`RewriterConflict`."""
self._sort()
self._check()

rewritten, pos = [], 0
for range, replacement in self.ranges:
rewritten.append(self.buffer.source[pos:range.begin_pos])
rewritten.append(replacement)
pos = range.end_pos
rewritten.append(self.buffer.source[pos:])

return Buffer(''.join(rewritten), self.buffer.name, self.buffer.first_line)

def _sort(self):
self.ranges.sort(key=lambda x: x[0].begin_pos)

def _check(self):
for (fst, _), (snd, _) in zip(self.ranges, self.ranges[1:]):
if snd.begin_pos < fst.end_pos:
raise RewriterConflict(fst, snd)

0 comments on commit fd7209a

Please sign in to comment.