ykit/buildtool/polly/patcher.py

103 lines
4 KiB
Python
Raw Normal View History

2024-08-19 22:58:17 +00:00
from dataclasses import dataclass
from typing import Any
from .parser import CharIndex, InsertionAction, PatchAction, Location, parse_patch_file
2024-08-17 17:14:45 +00:00
from pathlib import Path
from task.util import *
2024-08-19 22:58:17 +00:00
# Converts nebulous locations to real positions
def evaluate_position(content: str, location: Location):
if isinstance(location, CharIndex):
return location.char
# Ineligent yet effective solution...
chars_read = 0
2024-08-17 17:14:45 +00:00
2024-08-19 22:58:17 +00:00
if location.line > len(content.split('\n')):
raise RuntimeError("Line number out of range")
for i, line in enumerate(content.split('\n')):
if location.line == i + 1:
if location.column > len(line):
raise RuntimeError("Column number out of range")
2024-08-17 17:38:23 +00:00
2024-08-19 22:58:17 +00:00
return chars_read + location.column - 1
2024-08-17 17:14:45 +00:00
2024-08-19 22:58:17 +00:00
chars_read += len(line) + 1 # Don't forget the newline character!!!
2024-08-17 17:38:23 +00:00
2024-08-19 22:58:17 +00:00
raise RuntimeError("Line number out of range")
@dataclass
class ProcessedInsertion:
position: int
content: str
patch_file_name: str
type ProcessedAction = ProcessedInsertion
def update_positions_of_patches(actions: list[ProcessedAction], at: int, by: int):
for action in actions:
if action.position < at:
continue
2024-08-17 17:14:45 +00:00
2024-08-19 22:58:17 +00:00
action.position += by
2024-08-17 17:38:23 +00:00
2024-08-19 22:58:17 +00:00
def process_all_patches(dir: Path | str, check_date: bool = True):
dir = Path(dir)
patches: dict[Path, list[ProcessedAction]] = {}
# First, we collect all actions from all patches in each patch file
for patch_file in dir.rglob("*.patch"):
pfrn = patch_file.relative_to(dir)
for patch in parse_patch_file(str(patch_file)):
patches[patch.target] = patches.get(patch.target, [])
src_file = EXTRACTED_DIR / patch.target
dest_file = PATCHED_RSC_DIR / patch.target
for action in patch.actions:
# Skip patch if it is older than the target file
if dest_file.exists() and src_file.stat().st_mtime <= dest_file.stat().st_mtime:
print(f"Skipped {pfrn}::{patch.target}")
2024-08-17 17:38:23 +00:00
continue
2024-08-19 22:58:17 +00:00
with open(src_file, 'r') as f:
content = f.read()
2024-08-17 17:38:23 +00:00
2024-08-19 22:58:17 +00:00
# We also consider the true position of each insertion, too
if isinstance(action, InsertionAction):
processed_action = ProcessedInsertion(position=evaluate_position(content, action.location), content=action.content, patch_file_name=str(pfrn))
else:
raise RuntimeError("I don't know how to deal with those kinds of patches")
patches[patch.target].append(processed_action)
# Now we actually do all the patching
for target_file, actions in patches.items():
src_file = EXTRACTED_DIR / target_file
dest_file = PATCHED_RSC_DIR / target_file
with open(src_file, 'r') as f:
content = f.read()
2024-08-17 20:16:36 +00:00
2024-08-19 22:58:17 +00:00
print(f"Processing patches for: {target_file}")
2024-08-17 20:16:36 +00:00
2024-08-19 22:58:17 +00:00
for action in actions:
if isinstance(action, ProcessedInsertion):
# Update the positions of patches which modify after this one, so that their position isn't scrambled by this one
position = action.position
# update_positions_of_patches(actions, position, len(action.content))
# Update content
content = content[:position] + action.content + content[position:]
print(f" |- {action.patch_file_name}")
else:
raise RuntimeError("I don't know how to deal with those kinds of patches")
2024-08-19 22:58:17 +00:00
# Commit changes
dest_file.parent.mkdir(parents=True, exist_ok=True)
with open(dest_file, 'w') as f:
_ = f.write(content)