1import re 2import random 3from pathlib import Path 4from typing import Callable, Iterable 5 6from cuj import CujGroup, CujStep, de_src 7 8 9class RegexModify(CujGroup): 10 """ 11 A pair of CujSteps, where the fist modifies the file and the 12 second reverts it 13 Attributes: 14 file: the file to be edited and reverted 15 pattern: the string that will be replaced, only the FIRST occurrence will be replaced 16 replace: a function that generates the replacement string 17 modify_type: types of modification 18 """ 19 20 def __init__( 21 self, file: Path, pattern: str, replacer: Callable[[], str], modify_type: str 22 ): 23 super().__init__(f"{modify_type} {de_src(file)}") 24 if not file.exists(): 25 raise RuntimeError(f"{file} does not exist") 26 self.file = file 27 self.pattern = pattern 28 self.replacer = replacer 29 30 def get_steps(self) -> Iterable[CujStep]: 31 original_text: str 32 33 def modify(): 34 nonlocal original_text 35 original_text = self.file.read_text() 36 modified_text = re.sub( 37 self.pattern, 38 self.replacer(), 39 original_text, 40 count=1, 41 flags=re.MULTILINE, 42 ) 43 self.file.write_text(modified_text) 44 45 def revert(): 46 self.file.write_text(original_text) 47 48 return [CujStep("", modify), CujStep("revert", revert)] 49 50 51def modify_private_method(file: Path) -> CujGroup: 52 pattern = r"(private static boolean.*{)" 53 54 def replacement(): 55 return r'\1 Log.d("Placeholder", "Placeholder{}");'.format( 56 random.randint(0, 10000000) 57 ) 58 59 modify_type = "modify_private_method" 60 return RegexModify(file, pattern, replacement, modify_type) 61 62 63def add_private_field(file: Path) -> CujGroup: 64 class_name = file.name.removesuffix('.java') 65 pattern = fr"(\bclass {class_name} [^{{]*{{)" 66 67 def replacement(): 68 return f"\\1\nprivate static final int FOO = {random.randint(0, 10_000_000)};\n" 69 70 modify_type = "add_private_field" 71 return RegexModify(file, pattern, replacement, modify_type) 72 73 74def add_public_api(file: Path) -> CujGroup: 75 class_name = file.name.removesuffix('.java') 76 pattern = fr"(\bclass {class_name} [^{{]*{{)" 77 78 def replacement(): 79 return f"\\1\n@android.annotation.SuppressLint(\"UnflaggedApi\")\npublic static final int BAZ = {random.randint(0, 10_000_000)};\n" 80 81 modify_type = "add_public_api" 82 return RegexModify(file, pattern, replacement, modify_type) 83 84 85def modify_resource(file: Path) -> CujGroup: 86 pattern = r">0<" 87 88 def replacement(): 89 return r">" + str(random.randint(0, 10000000)) + r"<" 90 91 modify_type = "modify_resource" 92 return RegexModify(file, pattern, replacement, modify_type) 93 94 95def add_resource(file: Path) -> CujGroup: 96 pattern = r"</resources>" 97 98 def replacement(): 99 return ( 100 r' <integer name="foo">' 101 + str(random.randint(0, 10000000)) 102 + r"</integer>\n</resources>" 103 ) 104 105 modify_type = "add_resource" 106 return RegexModify(file, pattern, replacement, modify_type) 107