TRX/docs/render_progress
2021-02-12 18:06:35 +01:00

250 lines
6.1 KiB
Python
Executable file

#!/usr/bin/python3
import re
import typing as T
from dataclasses import dataclass
from pathlib import Path
GRID_MAX_SQUARES = 50
GRID_SQUARE_SIZE = 12
GRID_SQUARE_MARGIN = 2
LEGEND_SQUARE_SIZE = 12
LEGEND_SQUARE_MARGIN = 2
LEGEND_ROW_PADDING = 3
LEGEND_MARGIN = 15
TEXT_SIZE = 15
TEXT_MARGIN = 5
DOCS_DIR = Path(__file__).parent
PROGRESS_TXT_FILE = DOCS_DIR / "progress.txt"
PROGRESS_SVG_FILE = DOCS_DIR / "progress.svg"
COLOR_DECOMPILED = "forestgreen"
COLOR_NAMED = "lightpink"
COLOR_TODO = "mistyrose"
@dataclass
class Function:
name: str
offset: int
size: int
flags: str
@property
def is_decompiled(self):
return "+" in self.flags
@property
def is_named(self):
return not self.name.startswith("sub_")
def collect_functions() -> T.Iterable[Function]:
for line in PROGRESS_TXT_FILE.open():
if line.startswith("#"):
continue
func_name, offset, size, flags = re.split(r"\s+", line.strip())
yield Function(
name=func_name,
offset=int(offset, 16),
size=int(size, 16),
flags=flags,
)
@dataclass
class BoundingBox:
x1: int
y1: int
x2: int
y2: int
class Shape:
@property
def bounds(self) -> BoundingBox:
raise NotImplementedError("not implemented")
def render(self) -> str:
raise NotImplementedError("not implemented")
def get_common_bbox(shapes: T.List[Shape]) -> BoundingBox:
return BoundingBox(
x1=min(shape.bounds.x1 for shape in shapes),
y1=min(shape.bounds.y1 for shape in shapes),
x2=max(shape.bounds.x2 for shape in shapes),
y2=max(shape.bounds.y2 for shape in shapes),
)
@dataclass
class Square(Shape):
x: int
y: int
color: str
size: int = GRID_SQUARE_SIZE
@property
def bounds(self) -> BoundingBox:
return BoundingBox(
x1=self.x, y1=self.y, x2=self.x + self.size, y2=self.y + self.size
)
def render(self) -> str:
return (
f"<rect "
f'width="{self.size}" '
f'height="{self.size}" '
f'x="{self.x}" '
f'y="{self.y}" '
f'fill="{self.color}"/>'
)
@dataclass
class Text(Shape):
x: int
y: int
text: str
@property
def bounds(self) -> BoundingBox:
return BoundingBox(
x1=self.x, y1=self.y, x2=self.x, y2=self.y + TEXT_SIZE
)
def render(self) -> str:
return (
f'<text alignment-baseline="central" '
f'x="{self.x}" y="{self.y + TEXT_SIZE/2}" '
f'style="font-family: sans-serif; font-size: {TEXT_SIZE}px">'
f"{self.text}"
f"</text>"
)
@dataclass
class LegendText(Shape):
x: int
y: int
color: str
text: str
@property
def _square(self) -> Square:
return Square(
x=self.x,
y=self.y + (TEXT_SIZE - LEGEND_SQUARE_SIZE) / 2,
color=self.color,
size=LEGEND_SQUARE_SIZE,
)
@property
def _text(self) -> Text:
return Text(
x=LEGEND_SQUARE_SIZE + TEXT_MARGIN,
y=self.y,
text=self.text,
)
@property
def bounds(self) -> BoundingBox:
return get_common_bbox([self._square, self._text])
def render(self) -> str:
return self._square.render() + self._text.render()
def render_svg(functions: T.List[Function]) -> T.Iterable[Shape]:
for i, function in enumerate(functions):
x = (i % GRID_MAX_SQUARES) * (GRID_SQUARE_SIZE + GRID_SQUARE_MARGIN)
y = (i // GRID_MAX_SQUARES) * (GRID_SQUARE_SIZE + GRID_SQUARE_MARGIN)
if function.is_decompiled:
color = COLOR_DECOMPILED
elif function.is_named:
color = COLOR_NAMED
else:
color = COLOR_TODO
yield Square(x=x, y=y, color=color)
y += GRID_SQUARE_SIZE + LEGEND_MARGIN
ready_count = sum(func.is_decompiled for func in functions)
named_count = sum(func.is_named for func in functions)
total_count = len(functions)
ready_size = sum(func.size for func in functions if func.is_decompiled)
named_size = sum(func.size for func in functions if func.is_named)
total_size = sum(func.size for func in functions)
for (color, text) in [
(
COLOR_DECOMPILED,
(
f"Functions decompiled (count): "
f"{ready_count/total_count:.02%}"
),
),
(
COLOR_DECOMPILED,
f"Functions decompiled (bytesize): {ready_size/total_size:.02%}",
),
(
COLOR_NAMED,
(
f"Functions not decompiled, but with known names (count): "
f"{named_count/total_count:.02%}"
),
),
(
COLOR_NAMED,
(
f"Functions not decompiled, but with known names (bytesize): "
f"{named_size/total_size:.02%}"
),
),
(
COLOR_TODO,
(
f"Functions not decompiled, with unknown names (count): "
f"{(total_count-ready_count-named_count)/total_count:.02%}"
),
),
(
COLOR_TODO,
(
f"Functions not decompiled, with unknown names (bytesize): "
f"{(total_size-ready_size-named_size)/total_size:.02%}"
),
),
]:
yield LegendText(
x=0,
y=y,
color=color,
text=text,
)
y += TEXT_SIZE + LEGEND_ROW_PADDING
def main() -> None:
functions = list(collect_functions())
with PROGRESS_SVG_FILE.open("w") as handle:
shapes = list(render_svg(functions))
bbox = get_common_bbox(shapes)
print(
f'<svg version="1.1" '
f'width="{bbox.x2}" '
f'height="{bbox.y2}" '
f'xmlns="http://www.w3.org/2000/svg">',
file=handle,
)
for shape in shapes:
print(shape.render(), file=handle)
print("</svg>", file=handle)
if __name__ == "__main__":
main()