frida 16.7.13 → 16.7.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -10
- package/build/BSDmakefile +6 -0
- package/build/Makefile +10 -0
- package/build/src/frida.d.ts +364 -0
- package/build/src/frida.js +962 -0
- package/build/src/frida_binding.d.ts +938 -0
- package/meson.build +34 -67
- package/package.json +30 -20
- package/scripts/fetch-abi-bits.py +15 -65
- package/scripts/install.js +5 -4
- package/src/addon.def +3 -0
- package/src/addon.symbols +2 -1
- package/src/addon.version +4 -0
- package/src/frida_bindgen/__init__.py +0 -0
- package/src/frida_bindgen/__main__.py +4 -0
- package/src/frida_bindgen/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/frida_bindgen/__pycache__/__main__.cpython-312.pyc +0 -0
- package/src/frida_bindgen/__pycache__/cli.cpython-312.pyc +0 -0
- package/src/frida_bindgen/__pycache__/codegen.cpython-312.pyc +0 -0
- package/src/frida_bindgen/__pycache__/customization.cpython-312.pyc +0 -0
- package/src/frida_bindgen/__pycache__/loader.cpython-312.pyc +0 -0
- package/src/frida_bindgen/__pycache__/model.cpython-312.pyc +0 -0
- package/src/frida_bindgen/assets/codegen_helpers.c +1970 -0
- package/src/frida_bindgen/assets/codegen_helpers.ts +100 -0
- package/src/frida_bindgen/assets/codegen_prototypes.h +78 -0
- package/src/frida_bindgen/assets/codegen_types.h +57 -0
- package/src/frida_bindgen/assets/customization_facade.exports +13 -0
- package/src/frida_bindgen/assets/customization_facade.ts +157 -0
- package/src/frida_bindgen/assets/customization_helpers.imports +2 -0
- package/src/frida_bindgen/assets/customization_helpers.ts +396 -0
- package/src/frida_bindgen/cli.py +96 -0
- package/src/frida_bindgen/codegen.py +2233 -0
- package/src/frida_bindgen/customization.py +924 -0
- package/src/frida_bindgen/loader.py +60 -0
- package/src/frida_bindgen/model.py +1357 -0
- package/src/meson.build +92 -27
- package/{lib/build.py → src/tsc.py} +12 -12
- package/src/win_delay_load_hook.c +56 -0
- package/subprojects/frida-core.wrap +1 -1
- package/test/data/index.ts +2 -2
- package/test/device.ts +1 -2
- package/test/device_manager.ts +1 -2
- package/test/labrat.ts +2 -2
- package/test/script.ts +12 -12
- package/test/session.ts +3 -3
- package/tsconfig.json +6 -11
- package/dist/application.d.ts +0 -81
- package/dist/application.js +0 -2
- package/dist/authentication.d.ts +0 -3
- package/dist/authentication.js +0 -2
- package/dist/bus.d.ts +0 -16
- package/dist/bus.js +0 -23
- package/dist/cancellable.d.ts +0 -15
- package/dist/cancellable.js +0 -41
- package/dist/child.d.ts +0 -16
- package/dist/child.js +0 -9
- package/dist/crash.d.ts +0 -10
- package/dist/crash.js +0 -2
- package/dist/device.d.ts +0 -156
- package/dist/device.js +0 -188
- package/dist/device_manager.d.ts +0 -25
- package/dist/device_manager.js +0 -42
- package/dist/endpoint_parameters.d.ts +0 -26
- package/dist/endpoint_parameters.js +0 -24
- package/dist/icon.d.ts +0 -14
- package/dist/icon.js +0 -2
- package/dist/index.d.ts +0 -161
- package/dist/index.js +0 -170
- package/dist/iostream.d.ts +0 -13
- package/dist/iostream.js +0 -73
- package/dist/native.d.ts +0 -1
- package/dist/native.js +0 -11
- package/dist/portal_membership.d.ts +0 -6
- package/dist/portal_membership.js +0 -12
- package/dist/portal_service.d.ts +0 -48
- package/dist/portal_service.js +0 -52
- package/dist/process.d.ts +0 -47
- package/dist/process.js +0 -2
- package/dist/relay.d.ts +0 -22
- package/dist/relay.js +0 -32
- package/dist/script.d.ts +0 -70
- package/dist/script.js +0 -266
- package/dist/service.d.ts +0 -16
- package/dist/service.js +0 -26
- package/dist/session.d.ts +0 -45
- package/dist/session.js +0 -73
- package/dist/signals.d.ts +0 -20
- package/dist/signals.js +0 -40
- package/dist/socket_address.d.ts +0 -25
- package/dist/socket_address.js +0 -2
- package/dist/spawn.d.ts +0 -4
- package/dist/spawn.js +0 -2
- package/dist/system_parameters.d.ts +0 -84
- package/dist/system_parameters.js +0 -2
- package/lib/application.ts +0 -98
- package/lib/authentication.ts +0 -3
- package/lib/bus.ts +0 -30
- package/lib/cancellable.ts +0 -48
- package/lib/child.ts +0 -15
- package/lib/crash.ts +0 -11
- package/lib/device.ts +0 -331
- package/lib/device_manager.ts +0 -69
- package/lib/endpoint_parameters.ts +0 -56
- package/lib/icon.ts +0 -15
- package/lib/index.ts +0 -316
- package/lib/iostream.ts +0 -78
- package/lib/meson.build +0 -53
- package/lib/native.ts +0 -9
- package/lib/portal_membership.ts +0 -10
- package/lib/portal_service.ts +0 -105
- package/lib/process.ts +0 -57
- package/lib/relay.ts +0 -44
- package/lib/script.ts +0 -361
- package/lib/service.ts +0 -34
- package/lib/session.ts +0 -113
- package/lib/signals.ts +0 -45
- package/lib/socket_address.ts +0 -35
- package/lib/spawn.ts +0 -4
- package/lib/system_parameters.ts +0 -103
- package/meson.options +0 -11
- package/src/addon.cc +0 -78
- package/src/application.cc +0 -148
- package/src/application.h +0 -31
- package/src/authentication.cc +0 -174
- package/src/authentication.h +0 -24
- package/src/bus.cc +0 -167
- package/src/bus.h +0 -33
- package/src/cancellable.cc +0 -117
- package/src/cancellable.h +0 -31
- package/src/child.cc +0 -150
- package/src/child.h +0 -32
- package/src/crash.cc +0 -122
- package/src/crash.h +0 -30
- package/src/device.cc +0 -1350
- package/src/device.h +0 -56
- package/src/device_manager.cc +0 -362
- package/src/device_manager.h +0 -35
- package/src/endpoint_parameters.cc +0 -171
- package/src/endpoint_parameters.h +0 -28
- package/src/glib_context.cc +0 -62
- package/src/glib_context.h +0 -29
- package/src/glib_object.cc +0 -25
- package/src/glib_object.h +0 -37
- package/src/iostream.cc +0 -243
- package/src/iostream.h +0 -30
- package/src/operation.h +0 -94
- package/src/portal_membership.cc +0 -100
- package/src/portal_membership.h +0 -26
- package/src/portal_service.cc +0 -401
- package/src/portal_service.h +0 -40
- package/src/process.cc +0 -135
- package/src/process.h +0 -30
- package/src/relay.cc +0 -139
- package/src/relay.h +0 -31
- package/src/runtime.cc +0 -568
- package/src/runtime.h +0 -69
- package/src/script.cc +0 -301
- package/src/script.h +0 -36
- package/src/service.cc +0 -224
- package/src/service.h +0 -36
- package/src/session.cc +0 -860
- package/src/session.h +0 -42
- package/src/signals.cc +0 -334
- package/src/signals.h +0 -47
- package/src/spawn.cc +0 -95
- package/src/spawn.h +0 -27
- package/src/usage_monitor.h +0 -117
- package/src/uv_context.cc +0 -118
- package/src/uv_context.h +0 -40
- package/src/win_delay_load_hook.cc +0 -63
- package/subprojects/nan.wrap +0 -9
- package/subprojects/packagefiles/nan.patch +0 -13
|
@@ -0,0 +1,1357 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import xml.etree.ElementTree as ET
|
|
4
|
+
from collections import OrderedDict, defaultdict
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from functools import cached_property
|
|
8
|
+
from typing import (Callable, Iterator, List, Mapping, Optional, Sequence,
|
|
9
|
+
Tuple, Union)
|
|
10
|
+
|
|
11
|
+
CORE_NAMESPACE = "http://www.gtk.org/introspection/core/1.0"
|
|
12
|
+
C_NAMESPACE = "http://www.gtk.org/introspection/c/1.0"
|
|
13
|
+
GLIB_NAMESPACE = "http://www.gtk.org/introspection/glib/1.0"
|
|
14
|
+
GIR_NAMESPACES = {"": CORE_NAMESPACE, "glib": GLIB_NAMESPACE}
|
|
15
|
+
|
|
16
|
+
CORE_TAG_PREFIX = f"{{{CORE_NAMESPACE}}}"
|
|
17
|
+
|
|
18
|
+
NUMERIC_GIR_TYPES = {
|
|
19
|
+
"gsize",
|
|
20
|
+
"gssize",
|
|
21
|
+
"gint",
|
|
22
|
+
"guint",
|
|
23
|
+
"glong",
|
|
24
|
+
"gulong",
|
|
25
|
+
"gint8",
|
|
26
|
+
"gint16",
|
|
27
|
+
"gint32",
|
|
28
|
+
"gint64",
|
|
29
|
+
"guint8",
|
|
30
|
+
"guint16",
|
|
31
|
+
"guint32",
|
|
32
|
+
"guint64",
|
|
33
|
+
"GType",
|
|
34
|
+
"GQuark",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
PRIMITIVE_GIR_TYPES = NUMERIC_GIR_TYPES | {
|
|
38
|
+
"gpointer",
|
|
39
|
+
"gboolean",
|
|
40
|
+
"gchar",
|
|
41
|
+
"utf8",
|
|
42
|
+
"utf8[]",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
ResolveTypeCallback = Callable[[str], Tuple[str, ET.Element]]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class Customizations:
|
|
50
|
+
custom_types: Mapping[str, CustomType] = field(default_factory=OrderedDict)
|
|
51
|
+
type_customizations: Mapping[str, TypeCustomizations] = field(
|
|
52
|
+
default_factory=OrderedDict
|
|
53
|
+
)
|
|
54
|
+
facade_exports: List[str] = field(default_factory=list)
|
|
55
|
+
facade_code: str = ""
|
|
56
|
+
helper_imports: List[str] = field(default_factory=list)
|
|
57
|
+
helper_code: str = ""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class Model:
|
|
62
|
+
namespace: Namespace
|
|
63
|
+
_object_types: OrderedDict[str, ObjectType]
|
|
64
|
+
enumerations: OrderedDict[str, Enumeration]
|
|
65
|
+
customizations: Customizations = field(default_factory=Customizations)
|
|
66
|
+
|
|
67
|
+
@cached_property
|
|
68
|
+
def object_types(self) -> OrderedDict[str, ObjectType]:
|
|
69
|
+
result = OrderedDict()
|
|
70
|
+
type_customizations = self.customizations.type_customizations
|
|
71
|
+
for k, v in self._object_types.items():
|
|
72
|
+
custom = type_customizations.get(k)
|
|
73
|
+
if custom is None or not custom.drop:
|
|
74
|
+
result[k] = v
|
|
75
|
+
return result
|
|
76
|
+
|
|
77
|
+
@cached_property
|
|
78
|
+
def public_types(self) -> OrderedDict[str, Union[ObjectType, Enumeration]]:
|
|
79
|
+
return OrderedDict(
|
|
80
|
+
[(k, v) for k, v in self.object_types.items() if v.is_public]
|
|
81
|
+
+ list(self.enumerations.items())
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
@cached_property
|
|
85
|
+
def interface_types_with_abstract_base(self) -> List[InterfaceObjectType]:
|
|
86
|
+
return [
|
|
87
|
+
t
|
|
88
|
+
for t in self.object_types.values()
|
|
89
|
+
if isinstance(t, InterfaceObjectType) and t.has_abstract_base
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
def resolve_object_type(self, name: str) -> ObjectType:
|
|
93
|
+
bare_name = name.split(".", maxsplit=1)[-1]
|
|
94
|
+
return self.object_types[bare_name]
|
|
95
|
+
|
|
96
|
+
def resolve_js_type(self, t: Type) -> str:
|
|
97
|
+
js = js_type_from_gir(t.name)
|
|
98
|
+
otype = self.object_types.get(js)
|
|
99
|
+
if otype is not None:
|
|
100
|
+
return otype.js_name
|
|
101
|
+
return js
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@dataclass
|
|
105
|
+
class Namespace:
|
|
106
|
+
name: str
|
|
107
|
+
identifier_prefixes: str
|
|
108
|
+
element: ET.Element
|
|
109
|
+
|
|
110
|
+
@cached_property
|
|
111
|
+
def type_elements(self) -> Mapping[str, ET.Element]:
|
|
112
|
+
result = {}
|
|
113
|
+
for toplevel in self.element.findall("./*[@name]", GIR_NAMESPACES):
|
|
114
|
+
name = toplevel.get("name")
|
|
115
|
+
result[name] = toplevel
|
|
116
|
+
for callback in toplevel.findall("./callback", GIR_NAMESPACES):
|
|
117
|
+
result[name + callback.get("name")] = callback
|
|
118
|
+
return result
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclass
|
|
122
|
+
class ObjectType:
|
|
123
|
+
name: str
|
|
124
|
+
c_type: str
|
|
125
|
+
get_type: str
|
|
126
|
+
type_struct: str
|
|
127
|
+
_parent: Optional[str]
|
|
128
|
+
_constructors: List[ET.Element]
|
|
129
|
+
_methods: List[ET.Element]
|
|
130
|
+
_properties: List[ET.Element]
|
|
131
|
+
_signals: List[ET.Element]
|
|
132
|
+
resolve_type: ResolveTypeCallback
|
|
133
|
+
|
|
134
|
+
model: Optional[Model]
|
|
135
|
+
|
|
136
|
+
@cached_property
|
|
137
|
+
def js_name(self) -> str:
|
|
138
|
+
custom = self.customizations
|
|
139
|
+
if custom is not None and custom.js_name is not None:
|
|
140
|
+
return custom.js_name
|
|
141
|
+
return self.name
|
|
142
|
+
|
|
143
|
+
@cached_property
|
|
144
|
+
def prefixed_js_name(self) -> str:
|
|
145
|
+
return f"_{self.js_name}" if self.needs_wrapper else self.js_name
|
|
146
|
+
|
|
147
|
+
@cached_property
|
|
148
|
+
def abstract_base_c_type(self) -> str:
|
|
149
|
+
return f"FdnAbstract{self.name}"
|
|
150
|
+
|
|
151
|
+
@cached_property
|
|
152
|
+
def parent(self) -> ObjectType:
|
|
153
|
+
if self._parent is None:
|
|
154
|
+
return None
|
|
155
|
+
return self.model.resolve_object_type(self._parent)
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def is_public(self) -> bool:
|
|
159
|
+
return not self.is_frida_list
|
|
160
|
+
|
|
161
|
+
@cached_property
|
|
162
|
+
def is_frida_options(self) -> bool:
|
|
163
|
+
return self.c_type.startswith("Frida") and self.c_type.endswith("Options")
|
|
164
|
+
|
|
165
|
+
@cached_property
|
|
166
|
+
def is_frida_list(self) -> bool:
|
|
167
|
+
return self.c_type.startswith("Frida") and self.c_type.endswith("List")
|
|
168
|
+
|
|
169
|
+
@cached_property
|
|
170
|
+
def needs_wrapper(self) -> bool:
|
|
171
|
+
custom = self.customizations
|
|
172
|
+
if custom is None:
|
|
173
|
+
return False
|
|
174
|
+
if custom.custom_code is not None:
|
|
175
|
+
return True
|
|
176
|
+
ctor = self.constructors[0] if self.constructors else None
|
|
177
|
+
if ctor is not None and ctor.needs_wrapper:
|
|
178
|
+
return True
|
|
179
|
+
return self.wrapped_methods or self.wrapped_signals
|
|
180
|
+
|
|
181
|
+
@cached_property
|
|
182
|
+
def customizations(self) -> Optional[ObjectTypeCustomizations]:
|
|
183
|
+
return self.model.customizations.type_customizations.get(self.name)
|
|
184
|
+
|
|
185
|
+
@cached_property
|
|
186
|
+
def c_symbol_prefix(self) -> str:
|
|
187
|
+
return f"fdn_{to_snake_case(self.name)}"
|
|
188
|
+
|
|
189
|
+
@cached_property
|
|
190
|
+
def abstract_base_c_symbol_prefix(self) -> str:
|
|
191
|
+
return f"fdn_abstract_{to_snake_case(self.name)}"
|
|
192
|
+
|
|
193
|
+
@cached_property
|
|
194
|
+
def c_cast_macro(self) -> str:
|
|
195
|
+
return to_macro_case(self.c_type)
|
|
196
|
+
|
|
197
|
+
@cached_property
|
|
198
|
+
def abstract_base_c_cast_macro(self) -> str:
|
|
199
|
+
return to_macro_case(self.abstract_base_c_type)
|
|
200
|
+
|
|
201
|
+
@cached_property
|
|
202
|
+
def constructors(self) -> List[Constructor]:
|
|
203
|
+
constructors = []
|
|
204
|
+
custom = self.customizations
|
|
205
|
+
for element in self._constructors:
|
|
206
|
+
if element.get("introspectable") == "0" or element.get("deprecated") == "1":
|
|
207
|
+
continue
|
|
208
|
+
|
|
209
|
+
name = element.get("name")
|
|
210
|
+
|
|
211
|
+
if custom is not None:
|
|
212
|
+
ccust = custom.constructor
|
|
213
|
+
if ccust is not None and ccust.drop:
|
|
214
|
+
continue
|
|
215
|
+
|
|
216
|
+
(
|
|
217
|
+
c_identifier,
|
|
218
|
+
finish_c_identifier,
|
|
219
|
+
param_list,
|
|
220
|
+
has_closure_param,
|
|
221
|
+
throws,
|
|
222
|
+
result_element,
|
|
223
|
+
) = extract_callable_details(element, element, self, self.resolve_type)
|
|
224
|
+
if has_closure_param or finish_c_identifier is not None:
|
|
225
|
+
continue
|
|
226
|
+
|
|
227
|
+
constructors.append(
|
|
228
|
+
Constructor(
|
|
229
|
+
name, c_identifier, finish_c_identifier, param_list, throws, self
|
|
230
|
+
)
|
|
231
|
+
)
|
|
232
|
+
return constructors
|
|
233
|
+
|
|
234
|
+
@cached_property
|
|
235
|
+
def methods(self) -> List[Method]:
|
|
236
|
+
methods = []
|
|
237
|
+
c_prop_names = {prop.c_name for prop in self.properties}
|
|
238
|
+
custom = self.customizations
|
|
239
|
+
for element in self._methods:
|
|
240
|
+
name = element.get("name")
|
|
241
|
+
|
|
242
|
+
if (
|
|
243
|
+
element.get("introspectable") == "0"
|
|
244
|
+
or name.startswith("_")
|
|
245
|
+
or name.endswith("_sync")
|
|
246
|
+
or name.endswith("_finish")
|
|
247
|
+
):
|
|
248
|
+
continue
|
|
249
|
+
|
|
250
|
+
if custom is not None:
|
|
251
|
+
mcust = custom.methods.get(name, None)
|
|
252
|
+
if mcust is not None and mcust.drop:
|
|
253
|
+
continue
|
|
254
|
+
|
|
255
|
+
finish_func = element.get(f"{{{GLIB_NAMESPACE}}}finish-func")
|
|
256
|
+
if finish_func is None:
|
|
257
|
+
finish_func = f"{name}_finish"
|
|
258
|
+
result_element = next(
|
|
259
|
+
(m for m in self._methods if m.get("name") == finish_func), element
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
(
|
|
263
|
+
c_identifier,
|
|
264
|
+
finish_c_identifier,
|
|
265
|
+
param_list,
|
|
266
|
+
has_closure_param,
|
|
267
|
+
throws,
|
|
268
|
+
result_element,
|
|
269
|
+
) = extract_callable_details(
|
|
270
|
+
element, result_element, self, self.resolve_type
|
|
271
|
+
)
|
|
272
|
+
if has_closure_param:
|
|
273
|
+
continue
|
|
274
|
+
|
|
275
|
+
retval_element = result_element.find(".//return-value", GIR_NAMESPACES)
|
|
276
|
+
rettype = extract_type_from_entity(retval_element, self.resolve_type)
|
|
277
|
+
if rettype is not None:
|
|
278
|
+
if rettype.is_frida_options:
|
|
279
|
+
continue
|
|
280
|
+
|
|
281
|
+
nullable = retval_element.get("nullable") == "1"
|
|
282
|
+
|
|
283
|
+
ownership_val = retval_element.get("transfer-ownership")
|
|
284
|
+
transfer_ownership = (
|
|
285
|
+
TransferOwnership[ownership_val]
|
|
286
|
+
if ownership_val is not None
|
|
287
|
+
else TransferOwnership.none
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
retval = ReturnValue(rettype, nullable, transfer_ownership, self)
|
|
291
|
+
else:
|
|
292
|
+
retval = None
|
|
293
|
+
|
|
294
|
+
if element.get(f"{{{GLIB_NAMESPACE}}}get-property") is not None:
|
|
295
|
+
is_property_accessor = True
|
|
296
|
+
else:
|
|
297
|
+
tokens = name.split("_", maxsplit=1)
|
|
298
|
+
is_property_accessor = (
|
|
299
|
+
len(tokens) == 2
|
|
300
|
+
and tokens[0] in {"get", "set"}
|
|
301
|
+
and tokens[1] in c_prop_names
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
methods.append(
|
|
305
|
+
Method(
|
|
306
|
+
name,
|
|
307
|
+
c_identifier,
|
|
308
|
+
finish_c_identifier,
|
|
309
|
+
param_list,
|
|
310
|
+
throws,
|
|
311
|
+
retval,
|
|
312
|
+
is_property_accessor,
|
|
313
|
+
self,
|
|
314
|
+
)
|
|
315
|
+
)
|
|
316
|
+
return methods
|
|
317
|
+
|
|
318
|
+
@cached_property
|
|
319
|
+
def wrapped_methods(self) -> List[Method]:
|
|
320
|
+
return [m for m in self.methods if m.needs_wrapper]
|
|
321
|
+
|
|
322
|
+
@cached_property
|
|
323
|
+
def properties(self) -> List[Property]:
|
|
324
|
+
properties = []
|
|
325
|
+
custom = self.customizations
|
|
326
|
+
for element in self._properties:
|
|
327
|
+
name = element.get("name")
|
|
328
|
+
|
|
329
|
+
if custom is not None:
|
|
330
|
+
pcust = custom.properties.get(name, None)
|
|
331
|
+
if pcust is not None and pcust.drop:
|
|
332
|
+
continue
|
|
333
|
+
|
|
334
|
+
c_name = name.replace("-", "_")
|
|
335
|
+
type = extract_type_from_entity(element, self.resolve_type)
|
|
336
|
+
if type.is_frida_options:
|
|
337
|
+
continue
|
|
338
|
+
writable = element.get("writable") == "1"
|
|
339
|
+
construct_only = element.get("construct-only") == "1"
|
|
340
|
+
|
|
341
|
+
getter = element.get("getter")
|
|
342
|
+
if getter is None:
|
|
343
|
+
getter = f"get_{c_name}"
|
|
344
|
+
|
|
345
|
+
setter = element.get("setter")
|
|
346
|
+
if setter is None and writable and not construct_only:
|
|
347
|
+
setter = f"set_{c_name}"
|
|
348
|
+
|
|
349
|
+
properties.append(
|
|
350
|
+
Property(
|
|
351
|
+
name,
|
|
352
|
+
c_name,
|
|
353
|
+
type,
|
|
354
|
+
writable,
|
|
355
|
+
construct_only,
|
|
356
|
+
getter,
|
|
357
|
+
setter,
|
|
358
|
+
self,
|
|
359
|
+
)
|
|
360
|
+
)
|
|
361
|
+
return properties
|
|
362
|
+
|
|
363
|
+
@cached_property
|
|
364
|
+
def signals(self) -> List[Signal]:
|
|
365
|
+
signals = []
|
|
366
|
+
custom = self.customizations
|
|
367
|
+
for element in self._signals:
|
|
368
|
+
name = element.get("name")
|
|
369
|
+
|
|
370
|
+
if custom is not None:
|
|
371
|
+
scust = custom.signals.get(name, None)
|
|
372
|
+
if scust is not None and scust.drop:
|
|
373
|
+
continue
|
|
374
|
+
|
|
375
|
+
c_name = name.replace("-", "_")
|
|
376
|
+
param_list = extract_parameters(
|
|
377
|
+
element.findall("./parameters/parameter", GIR_NAMESPACES),
|
|
378
|
+
nullable_implies_optional=False,
|
|
379
|
+
object_type=self,
|
|
380
|
+
resolve_type=self.resolve_type,
|
|
381
|
+
)
|
|
382
|
+
signals.append(Signal(name, c_name, param_list, self))
|
|
383
|
+
return signals
|
|
384
|
+
|
|
385
|
+
@cached_property
|
|
386
|
+
def wrapped_signals(self) -> List[Signal]:
|
|
387
|
+
return [s for s in self.signals if s.needs_wrapper]
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
@dataclass
|
|
391
|
+
class ClassObjectType(ObjectType):
|
|
392
|
+
_implements: List[str]
|
|
393
|
+
|
|
394
|
+
@cached_property
|
|
395
|
+
def implements(self) -> List[InterfaceObjectType]:
|
|
396
|
+
return [self.model.resolve_object_type(i) for i in self._implements]
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
@dataclass
|
|
400
|
+
class InterfaceObjectType(ObjectType):
|
|
401
|
+
@cached_property
|
|
402
|
+
def has_abstract_base(self) -> bool:
|
|
403
|
+
custom = self.customizations
|
|
404
|
+
if custom is None:
|
|
405
|
+
return True
|
|
406
|
+
return not custom.drop_abstract_base
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
@dataclass
|
|
410
|
+
class Procedure:
|
|
411
|
+
name: str
|
|
412
|
+
c_identifier: str
|
|
413
|
+
finish_c_identifier: Optional[str]
|
|
414
|
+
parameters: List[Parameter]
|
|
415
|
+
throws: bool
|
|
416
|
+
|
|
417
|
+
@property
|
|
418
|
+
def is_async(self) -> bool:
|
|
419
|
+
return self.finish_c_identifier is not None
|
|
420
|
+
|
|
421
|
+
@cached_property
|
|
422
|
+
def input_parameters(self) -> List[Parameter]:
|
|
423
|
+
return [p for p in self.parameters if p.direction != Direction.OUT]
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
@dataclass
|
|
427
|
+
class Constructor(Procedure):
|
|
428
|
+
object_type: ObjectType
|
|
429
|
+
|
|
430
|
+
@cached_property
|
|
431
|
+
def param_typings(self) -> List[str]:
|
|
432
|
+
custom = self.customizations
|
|
433
|
+
if custom is not None and custom.param_typings is not None:
|
|
434
|
+
return custom.param_typings
|
|
435
|
+
return [param.typing for param in self.parameters]
|
|
436
|
+
|
|
437
|
+
@property
|
|
438
|
+
def needs_wrapper(self) -> bool:
|
|
439
|
+
custom = self.customizations
|
|
440
|
+
if custom is None:
|
|
441
|
+
return False
|
|
442
|
+
return custom.custom_logic is not None
|
|
443
|
+
|
|
444
|
+
@cached_property
|
|
445
|
+
def customizations(self) -> Optional[ConstructorCustomizations]:
|
|
446
|
+
custom = self.object_type.customizations
|
|
447
|
+
if custom is None:
|
|
448
|
+
return None
|
|
449
|
+
return custom.constructor
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
@dataclass
|
|
453
|
+
class Method(Procedure):
|
|
454
|
+
return_value: Optional[ReturnValue]
|
|
455
|
+
is_property_accessor: bool
|
|
456
|
+
|
|
457
|
+
object_type: ObjectType
|
|
458
|
+
|
|
459
|
+
@cached_property
|
|
460
|
+
def js_name(self) -> str:
|
|
461
|
+
custom = self.customizations
|
|
462
|
+
if custom is not None and custom.js_name is not None:
|
|
463
|
+
return custom.js_name
|
|
464
|
+
return to_camel_case(self.name)
|
|
465
|
+
|
|
466
|
+
@cached_property
|
|
467
|
+
def prefixed_js_name(self) -> str:
|
|
468
|
+
custom = self.customizations
|
|
469
|
+
if self.needs_wrapper or (custom is not None and custom.hide):
|
|
470
|
+
return f"_{self.js_name}"
|
|
471
|
+
return self.js_name
|
|
472
|
+
|
|
473
|
+
@cached_property
|
|
474
|
+
def cself_name(self) -> str:
|
|
475
|
+
return to_snake_case(self.object_type.name).split("_")[-1]
|
|
476
|
+
|
|
477
|
+
@cached_property
|
|
478
|
+
def param_ctypings(self) -> List[str]:
|
|
479
|
+
result = [f"{self.object_type.c_type} * {self.cself_name}"]
|
|
480
|
+
result += [param.ctyping for param in self.parameters]
|
|
481
|
+
if self.is_async:
|
|
482
|
+
result += ["GAsyncReadyCallback callback", "gpointer user_data"]
|
|
483
|
+
return result
|
|
484
|
+
|
|
485
|
+
@cached_property
|
|
486
|
+
def finish_param_ctypings(self) -> List[str]:
|
|
487
|
+
result = [
|
|
488
|
+
f"{self.object_type.c_type} * {self.cself_name}",
|
|
489
|
+
"GAsyncResult * result",
|
|
490
|
+
]
|
|
491
|
+
if self.throws:
|
|
492
|
+
result.append("GError ** error")
|
|
493
|
+
return result
|
|
494
|
+
|
|
495
|
+
@cached_property
|
|
496
|
+
def param_typings(self) -> List[str]:
|
|
497
|
+
custom = self.customizations
|
|
498
|
+
if custom is not None and custom.param_typings is not None:
|
|
499
|
+
return custom.param_typings
|
|
500
|
+
return self.prefixed_param_typings
|
|
501
|
+
|
|
502
|
+
@cached_property
|
|
503
|
+
def prefixed_param_typings(self) -> List[str]:
|
|
504
|
+
return [param.typing for param in self.input_parameters]
|
|
505
|
+
|
|
506
|
+
@cached_property
|
|
507
|
+
def return_ctyping(self) -> str:
|
|
508
|
+
retval = self.return_value
|
|
509
|
+
return retval.ctyping if retval is not None else "void"
|
|
510
|
+
|
|
511
|
+
@cached_property
|
|
512
|
+
def return_typing(self) -> str:
|
|
513
|
+
custom = self.customizations
|
|
514
|
+
if custom is not None and custom.return_typing is not None:
|
|
515
|
+
return custom.return_typing
|
|
516
|
+
return self.prefixed_return_typing
|
|
517
|
+
|
|
518
|
+
@cached_property
|
|
519
|
+
def prefixed_return_typing(self) -> str:
|
|
520
|
+
retval = self.return_value
|
|
521
|
+
typing = retval.typing if retval is not None else "void"
|
|
522
|
+
return f"Promise<{typing}>" if self.is_async else typing
|
|
523
|
+
|
|
524
|
+
@property
|
|
525
|
+
def needs_wrapper(self) -> bool:
|
|
526
|
+
custom = self.customizations
|
|
527
|
+
if custom is None:
|
|
528
|
+
return False
|
|
529
|
+
return custom.custom_logic is not None or custom.return_wrapper is not None
|
|
530
|
+
|
|
531
|
+
@cached_property
|
|
532
|
+
def customizations(self) -> Optional[MethodCustomizations]:
|
|
533
|
+
custom = self.object_type.customizations
|
|
534
|
+
if custom is None:
|
|
535
|
+
return None
|
|
536
|
+
return custom.methods.get(self.name)
|
|
537
|
+
|
|
538
|
+
@cached_property
|
|
539
|
+
def operation_type_name(self) -> str:
|
|
540
|
+
return f"Fdn{self.object_type.name}{to_pascal_case(self.name)}Operation"
|
|
541
|
+
|
|
542
|
+
@cached_property
|
|
543
|
+
def abstract_base_operation_type_name(self) -> str:
|
|
544
|
+
return f"FdnAbstract{self.object_type.name}{to_pascal_case(self.name)}Operation"
|
|
545
|
+
|
|
546
|
+
@cached_property
|
|
547
|
+
def is_select_method(self) -> bool:
|
|
548
|
+
return self.name.startswith("select_") or self.name.startswith("add_")
|
|
549
|
+
|
|
550
|
+
@cached_property
|
|
551
|
+
def select_noun(self) -> str:
|
|
552
|
+
assert (
|
|
553
|
+
self.is_select_method
|
|
554
|
+
), "select_noun can only be called on selector methods"
|
|
555
|
+
return self.name.split("_", maxsplit=1)[1]
|
|
556
|
+
|
|
557
|
+
@cached_property
|
|
558
|
+
def select_plural_noun(self) -> str:
|
|
559
|
+
return f"{self.select_noun}s"
|
|
560
|
+
|
|
561
|
+
@cached_property
|
|
562
|
+
def select_element_type(self) -> Type:
|
|
563
|
+
assert (
|
|
564
|
+
self.is_select_method
|
|
565
|
+
), "select_element_type can only be called on selector methods"
|
|
566
|
+
return self.parameters[0].type
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
@dataclass
|
|
570
|
+
class Property:
|
|
571
|
+
name: str
|
|
572
|
+
c_name: str
|
|
573
|
+
type: Type
|
|
574
|
+
writable: bool
|
|
575
|
+
construct_only: bool
|
|
576
|
+
getter: Optional[str]
|
|
577
|
+
setter: Optional[str]
|
|
578
|
+
|
|
579
|
+
object_type: ObjectType
|
|
580
|
+
|
|
581
|
+
@cached_property
|
|
582
|
+
def js_name(self) -> str:
|
|
583
|
+
custom = self.customizations
|
|
584
|
+
if custom is not None and custom.js_name is not None:
|
|
585
|
+
return custom.js_name
|
|
586
|
+
return to_camel_case(self.c_name)
|
|
587
|
+
|
|
588
|
+
@cached_property
|
|
589
|
+
def typing(self) -> str:
|
|
590
|
+
custom = self.customizations
|
|
591
|
+
if custom is not None and custom.typing is not None:
|
|
592
|
+
return custom.typing
|
|
593
|
+
readonly = "readonly " if not self.writable else ""
|
|
594
|
+
optional_str = "?" if self.object_type.is_frida_options else ""
|
|
595
|
+
return f"{readonly}{self.js_name}{optional_str}: {self.object_type.model.resolve_js_type(self.type)}"
|
|
596
|
+
|
|
597
|
+
@cached_property
|
|
598
|
+
def customizations(self) -> Optional[PropertyCustomizations]:
|
|
599
|
+
custom = self.object_type.customizations
|
|
600
|
+
if custom is None:
|
|
601
|
+
return None
|
|
602
|
+
return custom.properties.get(self.name)
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
@dataclass
|
|
606
|
+
class Signal:
|
|
607
|
+
name: str
|
|
608
|
+
c_name: str
|
|
609
|
+
parameters: List[Parameter]
|
|
610
|
+
|
|
611
|
+
object_type: ObjectType
|
|
612
|
+
|
|
613
|
+
@cached_property
|
|
614
|
+
def js_name(self) -> str:
|
|
615
|
+
return to_camel_case(self.c_name)
|
|
616
|
+
|
|
617
|
+
@cached_property
|
|
618
|
+
def prefixed_js_name(self) -> str:
|
|
619
|
+
return f"_{self.js_name}" if self.needs_wrapper else self.js_name
|
|
620
|
+
|
|
621
|
+
@cached_property
|
|
622
|
+
def handler_type_name(self) -> str:
|
|
623
|
+
# XXX: Special-cases to avoid breaking API:
|
|
624
|
+
class_name = self.object_type.name
|
|
625
|
+
if class_name == "DeviceManager":
|
|
626
|
+
prefix = "Device"
|
|
627
|
+
elif class_name == "Device":
|
|
628
|
+
prefix = "Device" if self.name == "lost" else ""
|
|
629
|
+
elif class_name == "PortalService":
|
|
630
|
+
prefix = "Portal"
|
|
631
|
+
elif class_name == "Cancellable":
|
|
632
|
+
prefix = ""
|
|
633
|
+
else:
|
|
634
|
+
prefix = class_name
|
|
635
|
+
return f"{prefix}{to_pascal_case(self.c_name)}Handler"
|
|
636
|
+
|
|
637
|
+
@cached_property
|
|
638
|
+
def prefixed_handler_type_name(self) -> str:
|
|
639
|
+
return (
|
|
640
|
+
f"_{self.handler_type_name}"
|
|
641
|
+
if self.needs_wrapper
|
|
642
|
+
else self.handler_type_name
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
@cached_property
|
|
646
|
+
def typing(self) -> str:
|
|
647
|
+
params = ", ".join([p.typing for p in self.parameters])
|
|
648
|
+
return f"({params}) => void"
|
|
649
|
+
|
|
650
|
+
@property
|
|
651
|
+
def needs_wrapper(self) -> bool:
|
|
652
|
+
custom = self.customizations
|
|
653
|
+
if custom is None:
|
|
654
|
+
return False
|
|
655
|
+
return custom.transform is not None or custom.intercept is not None
|
|
656
|
+
|
|
657
|
+
@cached_property
|
|
658
|
+
def customizations(self) -> Optional[SignalCustomizations]:
|
|
659
|
+
custom = self.object_type.customizations
|
|
660
|
+
if custom is None:
|
|
661
|
+
return None
|
|
662
|
+
return custom.signals.get(self.name)
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
TransferOwnership = Enum("TransferOwnership", ["none", "full", "container"])
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
@dataclass
|
|
669
|
+
class Parameter:
|
|
670
|
+
name: str
|
|
671
|
+
type: Type
|
|
672
|
+
optional: bool
|
|
673
|
+
nullable: bool
|
|
674
|
+
transfer_ownership: TransferOwnership
|
|
675
|
+
direction: Direction
|
|
676
|
+
|
|
677
|
+
object_type: ObjectType
|
|
678
|
+
|
|
679
|
+
@cached_property
|
|
680
|
+
def js_name(self) -> str:
|
|
681
|
+
return to_camel_case(self.name)
|
|
682
|
+
|
|
683
|
+
@cached_property
|
|
684
|
+
def ctyping(self) -> str:
|
|
685
|
+
return f"{self.type.c} {self.name}"
|
|
686
|
+
|
|
687
|
+
@cached_property
|
|
688
|
+
def typing(self) -> str:
|
|
689
|
+
optional_str = "?" if self.optional else ""
|
|
690
|
+
t = f"{self.js_name}{optional_str}: {self.object_type.model.resolve_js_type(self.type)}"
|
|
691
|
+
if self.nullable and not self.type.is_frida_options:
|
|
692
|
+
t += " | null"
|
|
693
|
+
return t
|
|
694
|
+
|
|
695
|
+
@cached_property
|
|
696
|
+
def copy_func(self) -> Optional[str]:
|
|
697
|
+
return self.type.copy_func
|
|
698
|
+
|
|
699
|
+
@cached_property
|
|
700
|
+
def destroy_func(self) -> Optional[str]:
|
|
701
|
+
return self.type.destroy_func
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
@dataclass
|
|
705
|
+
class ReturnValue:
|
|
706
|
+
type: Type
|
|
707
|
+
nullable: bool
|
|
708
|
+
transfer_ownership: TransferOwnership
|
|
709
|
+
|
|
710
|
+
object_type: ObjectType
|
|
711
|
+
|
|
712
|
+
@cached_property
|
|
713
|
+
def ctyping(self) -> str:
|
|
714
|
+
return self.type.c
|
|
715
|
+
|
|
716
|
+
@cached_property
|
|
717
|
+
def typing(self) -> str:
|
|
718
|
+
t = self.object_type.model.resolve_js_type(self.type)
|
|
719
|
+
if self.nullable:
|
|
720
|
+
t += " | null"
|
|
721
|
+
return t
|
|
722
|
+
|
|
723
|
+
@cached_property
|
|
724
|
+
def destroy_func(self) -> Optional[str]:
|
|
725
|
+
if self.transfer_ownership == TransferOwnership.none:
|
|
726
|
+
return None
|
|
727
|
+
return self.type.destroy_func
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
@dataclass
|
|
731
|
+
class Type:
|
|
732
|
+
name: str
|
|
733
|
+
nick: str
|
|
734
|
+
c: str
|
|
735
|
+
default_value: Optional[str]
|
|
736
|
+
copy_func: Optional[str]
|
|
737
|
+
destroy_func: Optional[str]
|
|
738
|
+
|
|
739
|
+
@cached_property
|
|
740
|
+
def from_pointer_func(self) -> Optional[str]:
|
|
741
|
+
if self.name in {"gssize", "gsize", "glong", "gulong", "gint64", "guint64"}:
|
|
742
|
+
return "GPOINTER_TO_SIZE"
|
|
743
|
+
if self.name in {"gint", "gint8", "gint16", "gint32"}:
|
|
744
|
+
return "GPOINTER_TO_INT"
|
|
745
|
+
if self.name in {"gboolean", "guint", "guint8", "guint16", "guint32"}:
|
|
746
|
+
return "GPOINTER_TO_UINT"
|
|
747
|
+
return None
|
|
748
|
+
|
|
749
|
+
@cached_property
|
|
750
|
+
def to_pointer_func(self) -> Optional[str]:
|
|
751
|
+
if self.name in {"gssize", "gsize", "glong", "gulong", "gint64", "guint64"}:
|
|
752
|
+
return "GSIZE_TO_POINTER"
|
|
753
|
+
if self.name in {"gint", "gint8", "gint16", "gint32"}:
|
|
754
|
+
return "GINT_TO_POINTER"
|
|
755
|
+
if self.name in {"gboolean", "guint", "guint8", "guint16", "guint32"}:
|
|
756
|
+
return "GUINT_TO_POINTER"
|
|
757
|
+
return None
|
|
758
|
+
|
|
759
|
+
@cached_property
|
|
760
|
+
def is_frida_options(self) -> bool:
|
|
761
|
+
return self.c.startswith("Frida") and self.c.endswith("Options *")
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
class Direction(Enum):
|
|
765
|
+
IN = "in"
|
|
766
|
+
OUT = "out"
|
|
767
|
+
INOUT = "inout"
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
@dataclass
|
|
771
|
+
class Enumeration:
|
|
772
|
+
name: str
|
|
773
|
+
c_type: str
|
|
774
|
+
get_type: str
|
|
775
|
+
_members: List[ET.Element]
|
|
776
|
+
|
|
777
|
+
model: Optional[Model]
|
|
778
|
+
|
|
779
|
+
@property
|
|
780
|
+
def js_name(self) -> str:
|
|
781
|
+
return self.name
|
|
782
|
+
|
|
783
|
+
@property
|
|
784
|
+
def prefixed_js_name(self) -> str:
|
|
785
|
+
return self.name
|
|
786
|
+
|
|
787
|
+
@cached_property
|
|
788
|
+
def members(self) -> List[EnumerationMember]:
|
|
789
|
+
members = []
|
|
790
|
+
for element in self._members:
|
|
791
|
+
members.append(EnumerationMember(element.get("name"), self))
|
|
792
|
+
return members
|
|
793
|
+
|
|
794
|
+
@property
|
|
795
|
+
def is_frida_options(self) -> bool:
|
|
796
|
+
return False
|
|
797
|
+
|
|
798
|
+
@cached_property
|
|
799
|
+
def customizations(self) -> Optional[EnumerationCustomizations]:
|
|
800
|
+
return self.model.customizations.type_customizations.get(self.name)
|
|
801
|
+
|
|
802
|
+
@cached_property
|
|
803
|
+
def c_symbol_prefix(self) -> str:
|
|
804
|
+
return f"fdn_{to_snake_case(self.name)}"
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
@dataclass
|
|
808
|
+
class EnumerationMember:
|
|
809
|
+
name: str
|
|
810
|
+
|
|
811
|
+
enumeration: Enumeration
|
|
812
|
+
|
|
813
|
+
@cached_property
|
|
814
|
+
def js_name(self) -> str:
|
|
815
|
+
custom = self.customizations
|
|
816
|
+
if custom is not None and custom.js_name is not None:
|
|
817
|
+
return custom.js_name
|
|
818
|
+
return to_pascal_case(self.name)
|
|
819
|
+
|
|
820
|
+
@cached_property
|
|
821
|
+
def nick(self) -> str:
|
|
822
|
+
return self.name.replace("_", "-")
|
|
823
|
+
|
|
824
|
+
@cached_property
|
|
825
|
+
def customizations(self) -> Optional[EnumerationMemberCustomizations]:
|
|
826
|
+
custom = self.enumeration.customizations
|
|
827
|
+
if custom is None:
|
|
828
|
+
return None
|
|
829
|
+
return custom.members.get(self.name)
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
@dataclass
|
|
833
|
+
class CustomType:
|
|
834
|
+
kind: CustomTypeKind
|
|
835
|
+
typing: str
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
class CustomTypeKind(Enum):
|
|
839
|
+
TYPE = "type"
|
|
840
|
+
INTERFACE = "interface"
|
|
841
|
+
ENUM = "enum"
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
@dataclass
|
|
845
|
+
class TypeCustomizations:
|
|
846
|
+
pass
|
|
847
|
+
|
|
848
|
+
|
|
849
|
+
@dataclass
|
|
850
|
+
class ObjectTypeCustomizations(TypeCustomizations):
|
|
851
|
+
js_name: Optional[str] = None
|
|
852
|
+
drop: bool = False
|
|
853
|
+
drop_abstract_base: bool = False
|
|
854
|
+
constructor: Optional[ConstructorCustomizations] = None
|
|
855
|
+
methods: Mapping[str, MethodCustomizations] = field(
|
|
856
|
+
default_factory=lambda: defaultdict(dict)
|
|
857
|
+
)
|
|
858
|
+
properties: Mapping[str, PropertyCustomizations] = field(
|
|
859
|
+
default_factory=lambda: defaultdict(dict)
|
|
860
|
+
)
|
|
861
|
+
signals: Mapping[str, SignalCustomizations] = field(
|
|
862
|
+
default_factory=lambda: defaultdict(dict)
|
|
863
|
+
)
|
|
864
|
+
custom_code: Optional[CustomCode] = None
|
|
865
|
+
cleanup: Optional[str] = None
|
|
866
|
+
keep_alive: Optional[KeepAliveCustomization] = None
|
|
867
|
+
|
|
868
|
+
|
|
869
|
+
@dataclass
|
|
870
|
+
class KeepAliveCustomization:
|
|
871
|
+
is_destroyed_function: str
|
|
872
|
+
destroy_signal_name: str
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
@dataclass
|
|
876
|
+
class ConstructorCustomizations:
|
|
877
|
+
drop: bool = False
|
|
878
|
+
param_typings: Optional[List[str]] = None
|
|
879
|
+
custom_logic: Optional[str] = None
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
@dataclass
|
|
883
|
+
class MethodCustomizations:
|
|
884
|
+
js_name: Optional[str] = None
|
|
885
|
+
drop: bool = False
|
|
886
|
+
hide: bool = False
|
|
887
|
+
param_typings: Optional[List[str]] = None
|
|
888
|
+
return_typing: Optional[str] = None
|
|
889
|
+
custom_logic: Optional[str] = None
|
|
890
|
+
return_wrapper: Optional[str] = None
|
|
891
|
+
return_cconversion: Optional[str] = None
|
|
892
|
+
ref_keep_alive: bool = False
|
|
893
|
+
unref_keep_alive: bool = False
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
@dataclass
|
|
897
|
+
class PropertyCustomizations:
|
|
898
|
+
js_name: Optional[str] = None
|
|
899
|
+
drop: bool = False
|
|
900
|
+
typing: Optional[str] = None
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
@dataclass
|
|
904
|
+
class SignalCustomizations:
|
|
905
|
+
drop: bool = False
|
|
906
|
+
behavior: str = "FDN_SIGNAL_ALLOW_EXIT"
|
|
907
|
+
transform: Optional[Mapping[int, Tuple[str, Optional[str]]]] = None
|
|
908
|
+
intercept: Optional[str] = None
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+
@dataclass
|
|
912
|
+
class CustomCode:
|
|
913
|
+
declarations: List[CustomDeclaration] = field(default_factory=list)
|
|
914
|
+
methods: List[CustomMethod] = field(default_factory=list)
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
@dataclass
|
|
918
|
+
class CustomDeclaration:
|
|
919
|
+
typing: Optional[str]
|
|
920
|
+
code: str
|
|
921
|
+
|
|
922
|
+
|
|
923
|
+
@dataclass
|
|
924
|
+
class CustomMethod:
|
|
925
|
+
typing: Optional[str]
|
|
926
|
+
code: str
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
@dataclass
|
|
930
|
+
class EnumerationCustomizations(TypeCustomizations):
|
|
931
|
+
members: Mapping[str, EnumerationMemberCustomizations] = field(
|
|
932
|
+
default_factory=lambda: defaultdict(dict)
|
|
933
|
+
)
|
|
934
|
+
|
|
935
|
+
|
|
936
|
+
@dataclass
|
|
937
|
+
class EnumerationMemberCustomizations:
|
|
938
|
+
js_name: Optional[str] = None
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
def parse_gir(file_path: str, dependencies: Sequence[Model]) -> Model:
|
|
942
|
+
tree = ET.parse(file_path)
|
|
943
|
+
|
|
944
|
+
el = tree.getroot().find("./namespace", GIR_NAMESPACES)
|
|
945
|
+
namespace = Namespace(
|
|
946
|
+
el.get("name"), el.get(f"{{{C_NAMESPACE}}}identifier-prefixes"), el
|
|
947
|
+
)
|
|
948
|
+
|
|
949
|
+
def resolve_type(name: str) -> Tuple[str, ET.Element]:
|
|
950
|
+
assert (
|
|
951
|
+
name not in PRIMITIVE_GIR_TYPES
|
|
952
|
+
), f"unexpectedly asked to resolve primitive type: {name}"
|
|
953
|
+
|
|
954
|
+
tokens = name.split(".", maxsplit=1)
|
|
955
|
+
if len(tokens) == 2:
|
|
956
|
+
ns_name, bare_name = tokens
|
|
957
|
+
if ns_name == namespace.name:
|
|
958
|
+
ns = namespace
|
|
959
|
+
else:
|
|
960
|
+
ns = next(
|
|
961
|
+
(
|
|
962
|
+
dep.namespace
|
|
963
|
+
for dep in dependencies
|
|
964
|
+
if dep.namespace.name == ns_name
|
|
965
|
+
),
|
|
966
|
+
None,
|
|
967
|
+
)
|
|
968
|
+
if ns is None:
|
|
969
|
+
assert ns is not None, f"unable to resolve namespace {ns_name}"
|
|
970
|
+
else:
|
|
971
|
+
ns = namespace
|
|
972
|
+
bare_name = name
|
|
973
|
+
qualified_name = f"{ns.name}.{bare_name}"
|
|
974
|
+
|
|
975
|
+
element = ns.type_elements.get(bare_name)
|
|
976
|
+
assert element is not None, f"unable to resolve type {bare_name}"
|
|
977
|
+
|
|
978
|
+
return (qualified_name, element)
|
|
979
|
+
|
|
980
|
+
object_types = OrderedDict()
|
|
981
|
+
|
|
982
|
+
for element in namespace.element.findall("./class", GIR_NAMESPACES):
|
|
983
|
+
name = element.get("name")
|
|
984
|
+
c_type = element.get(f"{{{C_NAMESPACE}}}type")
|
|
985
|
+
get_type = element.get(f"{{{GLIB_NAMESPACE}}}get-type")
|
|
986
|
+
type_struct = element.get(f"{{{GLIB_NAMESPACE}}}type-struct")
|
|
987
|
+
if type_struct is not None:
|
|
988
|
+
type_struct = namespace.identifier_prefixes + type_struct
|
|
989
|
+
else:
|
|
990
|
+
type_struct = c_type + "Class"
|
|
991
|
+
parent = element.get("parent")
|
|
992
|
+
if parent is not None:
|
|
993
|
+
parent, _ = resolve_type(parent)
|
|
994
|
+
constructors = element.findall(".//constructor", GIR_NAMESPACES)
|
|
995
|
+
methods = element.findall(".//method", GIR_NAMESPACES)
|
|
996
|
+
properties = element.findall(".//property", GIR_NAMESPACES)
|
|
997
|
+
signals = element.findall(".//glib:signal", GIR_NAMESPACES)
|
|
998
|
+
implements = [
|
|
999
|
+
e.get("name") for e in element.findall(".//implements", GIR_NAMESPACES)
|
|
1000
|
+
]
|
|
1001
|
+
|
|
1002
|
+
object_types[name] = ClassObjectType(
|
|
1003
|
+
name,
|
|
1004
|
+
c_type,
|
|
1005
|
+
get_type,
|
|
1006
|
+
type_struct,
|
|
1007
|
+
parent,
|
|
1008
|
+
constructors,
|
|
1009
|
+
methods,
|
|
1010
|
+
properties,
|
|
1011
|
+
signals,
|
|
1012
|
+
resolve_type,
|
|
1013
|
+
None,
|
|
1014
|
+
implements,
|
|
1015
|
+
)
|
|
1016
|
+
|
|
1017
|
+
for element in namespace.element.findall("./interface", GIR_NAMESPACES):
|
|
1018
|
+
name = element.get("name")
|
|
1019
|
+
c_type = element.get(f"{{{C_NAMESPACE}}}type")
|
|
1020
|
+
get_type = element.get(f"{{{GLIB_NAMESPACE}}}get-type")
|
|
1021
|
+
type_struct = element.get(f"{{{GLIB_NAMESPACE}}}type-struct")
|
|
1022
|
+
if type_struct is not None:
|
|
1023
|
+
type_struct = namespace.identifier_prefixes + type_struct
|
|
1024
|
+
else:
|
|
1025
|
+
type_struct = c_type + "Iface"
|
|
1026
|
+
prereq = element.find(".//prerequisite", GIR_NAMESPACES)
|
|
1027
|
+
parent = prereq.get("name") if prereq is not None else None
|
|
1028
|
+
if parent is not None:
|
|
1029
|
+
parent, _ = resolve_type(parent)
|
|
1030
|
+
constructors = []
|
|
1031
|
+
methods = element.findall(".//method", GIR_NAMESPACES)
|
|
1032
|
+
properties = element.findall(".//property", GIR_NAMESPACES)
|
|
1033
|
+
signals = element.findall(".//glib:signal", GIR_NAMESPACES)
|
|
1034
|
+
|
|
1035
|
+
object_types[name] = InterfaceObjectType(
|
|
1036
|
+
name,
|
|
1037
|
+
c_type,
|
|
1038
|
+
get_type,
|
|
1039
|
+
type_struct,
|
|
1040
|
+
parent,
|
|
1041
|
+
constructors,
|
|
1042
|
+
methods,
|
|
1043
|
+
properties,
|
|
1044
|
+
signals,
|
|
1045
|
+
resolve_type,
|
|
1046
|
+
None,
|
|
1047
|
+
)
|
|
1048
|
+
|
|
1049
|
+
enumerations = OrderedDict()
|
|
1050
|
+
|
|
1051
|
+
for element in namespace.element.findall("./enumeration", GIR_NAMESPACES):
|
|
1052
|
+
if element.get(f"{{{GLIB_NAMESPACE}}}error-domain") is not None:
|
|
1053
|
+
continue
|
|
1054
|
+
enum_name = element.get("name")
|
|
1055
|
+
enum_c_type = element.get(f"{{{C_NAMESPACE}}}type")
|
|
1056
|
+
get_type = element.get(f"{{{GLIB_NAMESPACE}}}get-type")
|
|
1057
|
+
members = element.findall(".//member", GIR_NAMESPACES)
|
|
1058
|
+
enumerations[enum_name] = Enumeration(
|
|
1059
|
+
enum_name, enum_c_type, get_type, members, None
|
|
1060
|
+
)
|
|
1061
|
+
|
|
1062
|
+
model = Model(namespace, object_types, enumerations)
|
|
1063
|
+
|
|
1064
|
+
for t in object_types.values():
|
|
1065
|
+
t.model = model
|
|
1066
|
+
for t in enumerations.values():
|
|
1067
|
+
t.model = model
|
|
1068
|
+
|
|
1069
|
+
return model
|
|
1070
|
+
|
|
1071
|
+
|
|
1072
|
+
def extract_callable_details(
|
|
1073
|
+
element: ET.Element,
|
|
1074
|
+
result_element: ET.Element,
|
|
1075
|
+
object_type: ObjectType,
|
|
1076
|
+
resolve_type: ResolveTypeCallback,
|
|
1077
|
+
) -> Tuple[str, Optional[str], List[Parameter], bool, bool, ET.Element]:
|
|
1078
|
+
c_identifier = element.get(f"{{{C_NAMESPACE}}}identifier")
|
|
1079
|
+
|
|
1080
|
+
parameters = element.findall("./parameters/parameter", GIR_NAMESPACES)
|
|
1081
|
+
full_param_list = extract_parameters(
|
|
1082
|
+
parameters,
|
|
1083
|
+
nullable_implies_optional=True,
|
|
1084
|
+
object_type=object_type,
|
|
1085
|
+
resolve_type=resolve_type,
|
|
1086
|
+
)
|
|
1087
|
+
param_list = list(all_regular_parameters(full_param_list))
|
|
1088
|
+
has_closure_param = any((param.get("closure") == "1" for param in parameters))
|
|
1089
|
+
|
|
1090
|
+
is_async = any(
|
|
1091
|
+
param.type.name == "Gio.AsyncReadyCallback" for param in full_param_list
|
|
1092
|
+
)
|
|
1093
|
+
if not is_async:
|
|
1094
|
+
result_element = element
|
|
1095
|
+
|
|
1096
|
+
finish_c_identifier = (
|
|
1097
|
+
result_element.get(f"{{{C_NAMESPACE}}}identifier") if is_async else None
|
|
1098
|
+
)
|
|
1099
|
+
|
|
1100
|
+
throws = result_element.get("throws") == "1"
|
|
1101
|
+
|
|
1102
|
+
return (
|
|
1103
|
+
c_identifier,
|
|
1104
|
+
finish_c_identifier,
|
|
1105
|
+
param_list,
|
|
1106
|
+
has_closure_param,
|
|
1107
|
+
throws,
|
|
1108
|
+
result_element,
|
|
1109
|
+
)
|
|
1110
|
+
|
|
1111
|
+
|
|
1112
|
+
def extract_parameters(
|
|
1113
|
+
parameter_elements: List[ET.Element],
|
|
1114
|
+
nullable_implies_optional: bool,
|
|
1115
|
+
object_type: ObjectType,
|
|
1116
|
+
resolve_type: ResolveTypeCallback,
|
|
1117
|
+
) -> List[Parameter]:
|
|
1118
|
+
entries = []
|
|
1119
|
+
for param in parameter_elements:
|
|
1120
|
+
nullable = param.get("nullable") == "1"
|
|
1121
|
+
entries.append((param, nullable))
|
|
1122
|
+
|
|
1123
|
+
last_required_index = None
|
|
1124
|
+
for i, (param, nullable) in enumerate(entries):
|
|
1125
|
+
optional = nullable and nullable_implies_optional
|
|
1126
|
+
if not optional:
|
|
1127
|
+
last_required_index = i
|
|
1128
|
+
|
|
1129
|
+
param_list = []
|
|
1130
|
+
for i, (param, nullable) in enumerate(entries):
|
|
1131
|
+
name = param.get("name")
|
|
1132
|
+
type = extract_type_from_entity(param, resolve_type)
|
|
1133
|
+
|
|
1134
|
+
if last_required_index is None or i > last_required_index:
|
|
1135
|
+
optional = nullable and nullable_implies_optional
|
|
1136
|
+
else:
|
|
1137
|
+
optional = False
|
|
1138
|
+
|
|
1139
|
+
ownership_val = param.get("transfer-ownership")
|
|
1140
|
+
transfer_ownership = (
|
|
1141
|
+
TransferOwnership[ownership_val]
|
|
1142
|
+
if ownership_val is not None
|
|
1143
|
+
else TransferOwnership.none
|
|
1144
|
+
)
|
|
1145
|
+
|
|
1146
|
+
raw_direction = param.get("direction")
|
|
1147
|
+
direction = (
|
|
1148
|
+
Direction(raw_direction) if raw_direction is not None else Direction.IN
|
|
1149
|
+
)
|
|
1150
|
+
|
|
1151
|
+
param_list.append(
|
|
1152
|
+
Parameter(
|
|
1153
|
+
name,
|
|
1154
|
+
type,
|
|
1155
|
+
optional,
|
|
1156
|
+
nullable,
|
|
1157
|
+
transfer_ownership,
|
|
1158
|
+
direction,
|
|
1159
|
+
object_type,
|
|
1160
|
+
)
|
|
1161
|
+
)
|
|
1162
|
+
return param_list
|
|
1163
|
+
|
|
1164
|
+
|
|
1165
|
+
def all_regular_parameters(parameters: List[Parameter]) -> Iterator[Parameter]:
|
|
1166
|
+
callback_index = None
|
|
1167
|
+
for i, param in enumerate(parameters):
|
|
1168
|
+
if param.type.name == "Gio.AsyncReadyCallback":
|
|
1169
|
+
callback_index = i
|
|
1170
|
+
continue
|
|
1171
|
+
|
|
1172
|
+
if callback_index is not None and i == callback_index + 1:
|
|
1173
|
+
continue
|
|
1174
|
+
|
|
1175
|
+
yield param
|
|
1176
|
+
|
|
1177
|
+
|
|
1178
|
+
def extract_type_from_entity(
|
|
1179
|
+
parent_element: ET.Element, resolve_type: ResolveTypeCallback
|
|
1180
|
+
) -> Optional[Type]:
|
|
1181
|
+
child = parent_element.find("type", GIR_NAMESPACES)
|
|
1182
|
+
if child is None:
|
|
1183
|
+
child = parent_element.find("array", GIR_NAMESPACES)
|
|
1184
|
+
assert child is not None
|
|
1185
|
+
element_type = extract_type_from_entity(child, resolve_type)
|
|
1186
|
+
if element_type.name == "utf8":
|
|
1187
|
+
return Type(
|
|
1188
|
+
"utf8[]",
|
|
1189
|
+
"strv",
|
|
1190
|
+
"gchar **",
|
|
1191
|
+
"NULL",
|
|
1192
|
+
"g_strdupv",
|
|
1193
|
+
"g_strfreev",
|
|
1194
|
+
)
|
|
1195
|
+
elif element_type.name == "gchar":
|
|
1196
|
+
return Type("char[]", "chararray", "gchar *", "NULL", "NULL", "NULL")
|
|
1197
|
+
elif element_type.name == "GObject.Value":
|
|
1198
|
+
return Type("Value[]", "valuearray", "GValue *", "NULL", "NULL", "NULL")
|
|
1199
|
+
else:
|
|
1200
|
+
assert (
|
|
1201
|
+
element_type.name == "guint8"
|
|
1202
|
+
), f"unsupported array type: {element_type.name}"
|
|
1203
|
+
return Type("uint8[]", "bytearray", "guint8 *", "NULL", "NULL", "NULL")
|
|
1204
|
+
|
|
1205
|
+
return parse_type(child, resolve_type)
|
|
1206
|
+
|
|
1207
|
+
|
|
1208
|
+
def parse_type(
|
|
1209
|
+
element: ET.Element, resolve_type: ResolveTypeCallback
|
|
1210
|
+
) -> Optional[Type]:
|
|
1211
|
+
name = element.get("name")
|
|
1212
|
+
assert name is not None
|
|
1213
|
+
if name == "none":
|
|
1214
|
+
return None
|
|
1215
|
+
|
|
1216
|
+
is_primitive = name in PRIMITIVE_GIR_TYPES
|
|
1217
|
+
c_type = element.get(f"{{{C_NAMESPACE}}}type")
|
|
1218
|
+
|
|
1219
|
+
core_tag = None
|
|
1220
|
+
if is_primitive:
|
|
1221
|
+
type_element = element
|
|
1222
|
+
if c_type is None:
|
|
1223
|
+
c_type = name
|
|
1224
|
+
else:
|
|
1225
|
+
name, type_element = resolve_type(name)
|
|
1226
|
+
if type_element.tag.startswith(CORE_TAG_PREFIX):
|
|
1227
|
+
core_tag = type_element.tag[len(CORE_TAG_PREFIX) :]
|
|
1228
|
+
c_type = type_element.get(f"{{{C_NAMESPACE}}}type")
|
|
1229
|
+
if core_tag in {"class", "interface", "record"}:
|
|
1230
|
+
c_type += "*"
|
|
1231
|
+
|
|
1232
|
+
nick = type_nick_from_name(name, element, resolve_type)
|
|
1233
|
+
c = c_type.replace("*", " *")
|
|
1234
|
+
|
|
1235
|
+
default_value = "NULL" if "*" in c else None
|
|
1236
|
+
|
|
1237
|
+
if name == "utf8":
|
|
1238
|
+
copy_func = "g_strdup"
|
|
1239
|
+
destroy_func = "g_free"
|
|
1240
|
+
elif name == "utf8[]":
|
|
1241
|
+
copy_func = "g_strdupv"
|
|
1242
|
+
destroy_func = "g_strfreev"
|
|
1243
|
+
elif name == "GLib.HashTable":
|
|
1244
|
+
copy_func = "g_hash_table_ref"
|
|
1245
|
+
destroy_func = "g_hash_table_unref"
|
|
1246
|
+
elif name == "GLib.Quark":
|
|
1247
|
+
copy_func = None
|
|
1248
|
+
destroy_func = None
|
|
1249
|
+
elif name == "GObject.Value":
|
|
1250
|
+
copy_func = "g_value_copy"
|
|
1251
|
+
destroy_func = "g_value_reset"
|
|
1252
|
+
elif name == "GObject.Closure":
|
|
1253
|
+
copy_func = "g_closure_ref"
|
|
1254
|
+
destroy_func = "g_closure_unref"
|
|
1255
|
+
elif core_tag in {"class", "interface"}:
|
|
1256
|
+
copy_func = "g_object_ref"
|
|
1257
|
+
destroy_func = "g_object_unref"
|
|
1258
|
+
elif is_primitive or core_tag in {"bitfield", "callback", "enumeration"}:
|
|
1259
|
+
copy_func = None
|
|
1260
|
+
destroy_func = None
|
|
1261
|
+
else:
|
|
1262
|
+
copy_func = type_element.get("copy-function")
|
|
1263
|
+
destroy_func = type_element.get("free-function")
|
|
1264
|
+
assert (
|
|
1265
|
+
destroy_func is not None
|
|
1266
|
+
), f"unable to resolve destroy function for {name}, core_tag={core_tag}"
|
|
1267
|
+
|
|
1268
|
+
return Type(name, nick, c, default_value, copy_func, destroy_func)
|
|
1269
|
+
|
|
1270
|
+
|
|
1271
|
+
def type_nick_from_name(
|
|
1272
|
+
name: str, element: ET.Element, resolve_type: ResolveTypeCallback
|
|
1273
|
+
) -> str:
|
|
1274
|
+
if name == "GLib.PollFD":
|
|
1275
|
+
return "pollfd"
|
|
1276
|
+
|
|
1277
|
+
tokens = name.split(".", maxsplit=1)
|
|
1278
|
+
if len(tokens) == 1:
|
|
1279
|
+
result = tokens[0]
|
|
1280
|
+
if result.startswith("g"):
|
|
1281
|
+
result = result[1:]
|
|
1282
|
+
else:
|
|
1283
|
+
result = to_snake_case(tokens[1])
|
|
1284
|
+
|
|
1285
|
+
if result == "hash_table":
|
|
1286
|
+
key_type = parse_type(element[0], resolve_type)
|
|
1287
|
+
value_type = parse_type(element[1], resolve_type)
|
|
1288
|
+
assert (
|
|
1289
|
+
key_type.name == "utf8" and value_type.name == "GLib.Variant"
|
|
1290
|
+
), "only GHashTable<string, Variant> is supported for now"
|
|
1291
|
+
result = "vardict"
|
|
1292
|
+
|
|
1293
|
+
return result
|
|
1294
|
+
|
|
1295
|
+
|
|
1296
|
+
def js_type_from_gir(name: str) -> str:
|
|
1297
|
+
if name == "gboolean":
|
|
1298
|
+
return "boolean"
|
|
1299
|
+
if name in NUMERIC_GIR_TYPES:
|
|
1300
|
+
return "number"
|
|
1301
|
+
if name == "utf8":
|
|
1302
|
+
return "string"
|
|
1303
|
+
if name == "utf8[]":
|
|
1304
|
+
return "string[]"
|
|
1305
|
+
if name == "GLib.Bytes":
|
|
1306
|
+
return "Buffer"
|
|
1307
|
+
if name == "GLib.HashTable":
|
|
1308
|
+
return "VariantDict"
|
|
1309
|
+
if name == "GLib.Variant":
|
|
1310
|
+
return "any"
|
|
1311
|
+
if name in {"Gio.File", "Gio.TlsCertificate"}:
|
|
1312
|
+
return "string"
|
|
1313
|
+
if name.startswith("Frida.") and name.endswith("List"):
|
|
1314
|
+
return name[6:-4] + "[]"
|
|
1315
|
+
return name.split(".")[-1]
|
|
1316
|
+
|
|
1317
|
+
|
|
1318
|
+
def to_snake_case(name: str) -> str:
|
|
1319
|
+
result = []
|
|
1320
|
+
i = 0
|
|
1321
|
+
n = len(name)
|
|
1322
|
+
while i < n:
|
|
1323
|
+
if name[i].isupper():
|
|
1324
|
+
if i > 0:
|
|
1325
|
+
result.append("_")
|
|
1326
|
+
start = i
|
|
1327
|
+
if i + 1 < n and name[i + 1].islower():
|
|
1328
|
+
while i + 1 < n and name[i + 1].islower():
|
|
1329
|
+
i += 1
|
|
1330
|
+
else:
|
|
1331
|
+
while i + 1 < n and name[i + 1].isupper():
|
|
1332
|
+
i += 1
|
|
1333
|
+
if i + 1 < n:
|
|
1334
|
+
i -= 1
|
|
1335
|
+
result.append(name[start : i + 1].lower())
|
|
1336
|
+
else:
|
|
1337
|
+
result.append(name[i])
|
|
1338
|
+
i += 1
|
|
1339
|
+
return "".join(result)
|
|
1340
|
+
|
|
1341
|
+
|
|
1342
|
+
def to_pascal_case(name: str) -> str:
|
|
1343
|
+
return "".join(word.capitalize() for word in name.split("_"))
|
|
1344
|
+
|
|
1345
|
+
|
|
1346
|
+
def to_camel_case(name: str) -> str:
|
|
1347
|
+
words = name.split("_")
|
|
1348
|
+
return words[0] + "".join(word.capitalize() for word in words[1:])
|
|
1349
|
+
|
|
1350
|
+
|
|
1351
|
+
def to_macro_case(identifier: str) -> str:
|
|
1352
|
+
result = []
|
|
1353
|
+
for i, char in enumerate(identifier):
|
|
1354
|
+
if char.isupper() and i != 0:
|
|
1355
|
+
result.append("_")
|
|
1356
|
+
result.append(char)
|
|
1357
|
+
return "".join(result).upper()
|