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