castre/castre.py

137 lines
3.2 KiB
Python

import bisect
import json
import os
import subprocess
def walk(args, walker, path_filter=None, fixer=None):
proc = subprocess.run(["clang++", "-Xclang", "-ast-dump=json", "-c", *args], capture_output=True)
if proc.returncode != 0:
print(proc.stderr.decode("utf-8"))
raise Exception("analysis failure")
tree = json.loads(proc.stdout)
root = Item(tree)
fixer = fixer if fixer is not None else Fixer()
file = None
path = None
for item in tree["inner"]:
if "file" in item["loc"]:
path = os.path.abspath(item["loc"]["file"])
file = None
if path_filter is not None and not path_filter(path):
continue
if file is None:
file = fixer.makeFile(path)
walker(Item(item, parent=root, file=file))
return fixer
class Item:
def __init__(self, j, parent=None, file=None):
self.parent = parent
self.raw = j
self.file = None
if parent is not None:
self.file = parent.file
if file is not None:
self.file = file
def __iter__(self):
return ItemItr(self)
def refactorable(self):
return self.file is not None and "file" not in self.raw["range"]["end"]
def range(self):
if not self.refactorable():
raise Exception("item is not refactorable")
ra = self.raw["range"]
begin = ra["begin"]["offset"]
end = ra["end"]["offset"]
return (begin, end)
def pos(self):
begin, end = self.range()
return (begin, end-begin)
def refactor(self, text):
self.file.replace(*self.pos(), text)
def insertBefore(self, text):
self.file.replace(range()[0], 0, text)
def insertAfter(self, text):
self.file.replace(range()[1], 0, text)
class ItemItr:
def __init__(self, item):
self.index = 0
self.item = item
self.inner = self.item.raw["inner"] if "inner" in self.item.raw else None
def __next__(self):
if self.inner == None or len(self.inner) <= self.index:
raise StopIteration()
ret = Item(self.inner[self.index], parent=self.item)
self.index += 1
return ret
class Fixer:
def __init__(self):
self.files = {}
def makeFile(self, path):
if path in self.files:
return self.files[path]
ret = File(path);
self.files[path] = ret
return ret
def fix(self):
for f in self.files.values():
f.fix()
class File:
def __init__(self, path):
self.path = path
self.tasks = []
def replace(self, offset, n, text):
idx = bisect.bisect(self.tasks, x=offset, key=lambda x: x[0])
if idx > 0:
prev = self.tasks[idx-1]
if prev[0]+prev[1] > offset:
raise Exception("change conflict")
if idx < len(self.tasks):
next = self.tasks[idx]
if next[0] < offset+n:
raise Exception ("change conflict")
self.tasks.insert(idx, (offset, n, text))
def dryFix(self):
with open(self.path, "r") as f:
src = f.read()
for task in reversed(self.tasks):
begin = task[0]
end = begin + task[1]
text = task[2]
if not isinstance(text, str):
text = text(src[begin:end])
src = src[0:begin] + text + src[end:]
return src
def fix(self):
src = self.dryFix()
with open(self.path, "w") as f:
f.write(src)