@gtkx/gir 0.1.0
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/package.json +21 -0
- package/src/index.ts +2 -0
- package/src/parser.ts +313 -0
- package/src/types.ts +469 -0
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gtkx/gir",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "GObject Introspection file parser for GTKX",
|
|
5
|
+
"license": "MPL-2.0",
|
|
6
|
+
"author": "Eugenio Depalo <eugeniodepalo@gmail.com>",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/eugeniodepalo/gtkx.git",
|
|
10
|
+
"directory": "packages/gir"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://eugeniodepalo.github.io/gtkx",
|
|
13
|
+
"bugs": "https://github.com/eugeniodepalo/gtkx/issues",
|
|
14
|
+
"keywords": ["gtk", "gtk4", "gir", "gobject", "introspection", "parser"],
|
|
15
|
+
"type": "module",
|
|
16
|
+
"exports": {
|
|
17
|
+
"./package.json": "./package.json",
|
|
18
|
+
".": "./src/index.ts"
|
|
19
|
+
},
|
|
20
|
+
"files": ["src"]
|
|
21
|
+
}
|
package/src/index.ts
ADDED
package/src/parser.ts
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { XMLParser } from "fast-xml-parser";
|
|
2
|
+
import type {
|
|
3
|
+
GirClass,
|
|
4
|
+
GirConstructor,
|
|
5
|
+
GirEnumeration,
|
|
6
|
+
GirEnumerationMember,
|
|
7
|
+
GirFunction,
|
|
8
|
+
GirInterface,
|
|
9
|
+
GirMethod,
|
|
10
|
+
GirNamespace,
|
|
11
|
+
GirParameter,
|
|
12
|
+
GirProperty,
|
|
13
|
+
GirSignal,
|
|
14
|
+
GirType,
|
|
15
|
+
} from "./types.js";
|
|
16
|
+
|
|
17
|
+
const ARRAY_ELEMENT_PATHS = new Set<string>([
|
|
18
|
+
"namespace.class",
|
|
19
|
+
"namespace.interface",
|
|
20
|
+
"namespace.function",
|
|
21
|
+
"namespace.enumeration",
|
|
22
|
+
"namespace.bitfield",
|
|
23
|
+
"namespace.class.method",
|
|
24
|
+
"namespace.class.constructor",
|
|
25
|
+
"namespace.class.property",
|
|
26
|
+
"namespace.class.signal",
|
|
27
|
+
"namespace.class.glib:signal",
|
|
28
|
+
"namespace.interface.method",
|
|
29
|
+
"namespace.interface.property",
|
|
30
|
+
"namespace.interface.signal",
|
|
31
|
+
"namespace.interface.glib:signal",
|
|
32
|
+
"namespace.class.method.parameters.parameter",
|
|
33
|
+
"namespace.class.constructor.parameters.parameter",
|
|
34
|
+
"namespace.function.parameters.parameter",
|
|
35
|
+
"namespace.enumeration.member",
|
|
36
|
+
"namespace.bitfield.member",
|
|
37
|
+
"namespace.interface.method.parameters.parameter",
|
|
38
|
+
"namespace.class.glib:signal.parameters.parameter",
|
|
39
|
+
"namespace.interface.glib:signal.parameters.parameter",
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
const extractDoc = (node: Record<string, unknown>): string | undefined => {
|
|
43
|
+
const doc = node.doc as Record<string, unknown> | undefined;
|
|
44
|
+
if (!doc) return undefined;
|
|
45
|
+
const text = doc["#text"] as string | undefined;
|
|
46
|
+
return text?.trim();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Parser for GObject Introspection (GIR) XML files.
|
|
51
|
+
* Converts GIR XML into structured TypeScript interfaces.
|
|
52
|
+
*/
|
|
53
|
+
export class GirParser {
|
|
54
|
+
private parser: XMLParser;
|
|
55
|
+
|
|
56
|
+
/** Creates a new GIR parser instance. */
|
|
57
|
+
constructor() {
|
|
58
|
+
this.parser = new XMLParser({
|
|
59
|
+
ignoreAttributes: false,
|
|
60
|
+
attributeNamePrefix: "@_",
|
|
61
|
+
textNodeName: "#text",
|
|
62
|
+
isArray: (_name, jpath, _isLeafNode, _isAttribute) => {
|
|
63
|
+
const path = jpath.split(".").slice(1).join(".");
|
|
64
|
+
return ARRAY_ELEMENT_PATHS.has(path);
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Parses a GIR XML string into a structured namespace definition.
|
|
71
|
+
* @param girXml - The GIR XML content to parse
|
|
72
|
+
* @returns The parsed namespace containing all type definitions
|
|
73
|
+
* @throws Error if the XML is invalid or missing required elements
|
|
74
|
+
*/
|
|
75
|
+
parse(girXml: string): GirNamespace {
|
|
76
|
+
const parsed = this.parser.parse(girXml);
|
|
77
|
+
const repository = parsed.repository;
|
|
78
|
+
|
|
79
|
+
if (!repository?.namespace) {
|
|
80
|
+
throw new Error("Invalid GIR file: missing repository or namespace");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const namespace = repository.namespace;
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
name: namespace["@_name"],
|
|
87
|
+
version: namespace["@_version"],
|
|
88
|
+
sharedLibrary: namespace["@_shared-library"] ?? "",
|
|
89
|
+
cPrefix: namespace["@_c:identifier-prefixes"] ?? namespace["@_c:prefix"] ?? "",
|
|
90
|
+
classes: this.parseClasses(namespace.class ?? []),
|
|
91
|
+
interfaces: this.parseInterfaces(namespace.interface ?? []),
|
|
92
|
+
functions: this.parseFunctions(namespace.function ?? []),
|
|
93
|
+
enumerations: this.parseEnumerations(namespace.enumeration ?? []),
|
|
94
|
+
bitfields: this.parseEnumerations(namespace.bitfield ?? []),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private parseClasses(classes: Record<string, unknown>[]): GirClass[] {
|
|
99
|
+
return classes.map((cls) => ({
|
|
100
|
+
name: String(cls["@_name"] ?? ""),
|
|
101
|
+
cType: String(cls["@_c:type"] ?? cls["@_glib:type-name"] ?? ""),
|
|
102
|
+
parent: String(cls["@_parent"] ?? ""),
|
|
103
|
+
abstract: cls["@_abstract"] === "1",
|
|
104
|
+
implements: this.parseImplements(
|
|
105
|
+
cls.implements as Record<string, unknown>[] | Record<string, unknown> | undefined,
|
|
106
|
+
),
|
|
107
|
+
methods: this.parseMethods(Array.isArray(cls.method) ? (cls.method as Record<string, unknown>[]) : []),
|
|
108
|
+
constructors: this.parseConstructors(
|
|
109
|
+
Array.isArray(cls.constructor) ? (cls.constructor as Record<string, unknown>[]) : [],
|
|
110
|
+
),
|
|
111
|
+
properties: this.parseProperties(
|
|
112
|
+
Array.isArray(cls.property) ? (cls.property as Record<string, unknown>[]) : [],
|
|
113
|
+
),
|
|
114
|
+
signals: this.parseSignals(
|
|
115
|
+
Array.isArray(cls["glib:signal"]) ? (cls["glib:signal"] as Record<string, unknown>[]) : [],
|
|
116
|
+
),
|
|
117
|
+
doc: extractDoc(cls),
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private parseImplements(implements_: Record<string, unknown>[] | Record<string, unknown> | undefined): string[] {
|
|
122
|
+
if (!implements_) return [];
|
|
123
|
+
const arr = Array.isArray(implements_) ? implements_ : [implements_];
|
|
124
|
+
return arr.map((impl) => String(impl["@_name"] ?? "")).filter(Boolean);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private parseInterfaces(interfaces: Record<string, unknown>[]): GirInterface[] {
|
|
128
|
+
if (!interfaces || !Array.isArray(interfaces)) {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
return interfaces.map((iface) => ({
|
|
132
|
+
name: String(iface["@_name"] ?? ""),
|
|
133
|
+
cType: String(iface["@_c:type"] ?? iface["@_glib:type-name"] ?? ""),
|
|
134
|
+
methods: this.parseMethods(Array.isArray(iface.method) ? (iface.method as Record<string, unknown>[]) : []),
|
|
135
|
+
properties: this.parseProperties(
|
|
136
|
+
Array.isArray(iface.property) ? (iface.property as Record<string, unknown>[]) : [],
|
|
137
|
+
),
|
|
138
|
+
signals: this.parseSignals(
|
|
139
|
+
Array.isArray(iface["glib:signal"]) ? (iface["glib:signal"] as Record<string, unknown>[]) : [],
|
|
140
|
+
),
|
|
141
|
+
doc: extractDoc(iface),
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private parseMethods(methods: Record<string, unknown>[]): GirMethod[] {
|
|
146
|
+
if (!methods || !Array.isArray(methods)) {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
return methods.map((method) => ({
|
|
150
|
+
name: String(method["@_name"] ?? ""),
|
|
151
|
+
cIdentifier: String(method["@_c:identifier"] ?? ""),
|
|
152
|
+
returnType: this.parseReturnType(method["return-value"] as Record<string, unknown> | undefined),
|
|
153
|
+
parameters: this.parseParameters(
|
|
154
|
+
(method.parameters && typeof method.parameters === "object" && method.parameters !== null
|
|
155
|
+
? method.parameters
|
|
156
|
+
: {}) as Record<string, unknown>,
|
|
157
|
+
),
|
|
158
|
+
throws: method["@_throws"] === "1",
|
|
159
|
+
doc: extractDoc(method),
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private parseConstructors(constructors: Record<string, unknown>[]): GirConstructor[] {
|
|
164
|
+
if (!constructors || !Array.isArray(constructors)) {
|
|
165
|
+
return [];
|
|
166
|
+
}
|
|
167
|
+
return constructors.map((ctor) => ({
|
|
168
|
+
name: String(ctor["@_name"] ?? ""),
|
|
169
|
+
cIdentifier: String(ctor["@_c:identifier"] ?? ""),
|
|
170
|
+
returnType: this.parseReturnType(ctor["return-value"] as Record<string, unknown> | undefined),
|
|
171
|
+
parameters: this.parseParameters(
|
|
172
|
+
(ctor.parameters && typeof ctor.parameters === "object" && ctor.parameters !== null
|
|
173
|
+
? ctor.parameters
|
|
174
|
+
: {}) as Record<string, unknown>,
|
|
175
|
+
),
|
|
176
|
+
doc: extractDoc(ctor),
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private parseFunctions(functions: Record<string, unknown>[]): GirFunction[] {
|
|
181
|
+
if (!functions || !Array.isArray(functions)) {
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
return functions.map((func) => ({
|
|
185
|
+
name: String(func["@_name"] ?? ""),
|
|
186
|
+
cIdentifier: String(func["@_c:identifier"] ?? ""),
|
|
187
|
+
returnType: this.parseReturnType(func["return-value"] as Record<string, unknown> | undefined),
|
|
188
|
+
parameters: this.parseParameters(
|
|
189
|
+
(func.parameters && typeof func.parameters === "object" && func.parameters !== null
|
|
190
|
+
? func.parameters
|
|
191
|
+
: {}) as Record<string, unknown>,
|
|
192
|
+
),
|
|
193
|
+
throws: func["@_throws"] === "1",
|
|
194
|
+
doc: extractDoc(func),
|
|
195
|
+
}));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private parseParameters(parametersNode: Record<string, unknown>): GirParameter[] {
|
|
199
|
+
if (!parametersNode?.parameter) {
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const params = Array.isArray(parametersNode.parameter) ? parametersNode.parameter : [parametersNode.parameter];
|
|
204
|
+
|
|
205
|
+
return params.map((param: Record<string, unknown>) => ({
|
|
206
|
+
name: String(param["@_name"] ?? ""),
|
|
207
|
+
type: this.parseType((param.type ?? param.array) as Record<string, unknown> | undefined),
|
|
208
|
+
direction: (String(param["@_direction"] ?? "in") as "in" | "out" | "inout") || "in",
|
|
209
|
+
nullable: param["@_nullable"] === "1",
|
|
210
|
+
optional: param["@_allow-none"] === "1",
|
|
211
|
+
doc: extractDoc(param),
|
|
212
|
+
}));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private parseReturnType(returnValue: Record<string, unknown> | undefined): GirType {
|
|
216
|
+
if (!returnValue) {
|
|
217
|
+
return { name: "void" };
|
|
218
|
+
}
|
|
219
|
+
return this.parseType((returnValue.type ?? returnValue.array) as Record<string, unknown> | undefined);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private parseType(typeNode: Record<string, unknown> | undefined): GirType {
|
|
223
|
+
if (!typeNode) {
|
|
224
|
+
return { name: "void" };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (typeNode["@_name"]) {
|
|
228
|
+
return {
|
|
229
|
+
name: String(typeNode["@_name"] ?? ""),
|
|
230
|
+
cType: typeNode["@_c:type"] ? String(typeNode["@_c:type"]) : undefined,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const isArrayNode =
|
|
235
|
+
typeNode.type ||
|
|
236
|
+
typeNode["@_zero-terminated"] !== undefined ||
|
|
237
|
+
typeNode["@_fixed-size"] !== undefined ||
|
|
238
|
+
typeNode["@_length"] !== undefined;
|
|
239
|
+
|
|
240
|
+
if (isArrayNode) {
|
|
241
|
+
return {
|
|
242
|
+
name: "array",
|
|
243
|
+
isArray: true,
|
|
244
|
+
elementType: typeNode.type ? this.parseType(typeNode.type as Record<string, unknown>) : undefined,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return { name: "void" };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private parseProperties(properties: Record<string, unknown>[]): GirProperty[] {
|
|
252
|
+
if (!properties || !Array.isArray(properties)) {
|
|
253
|
+
return [];
|
|
254
|
+
}
|
|
255
|
+
return properties.map((prop) => ({
|
|
256
|
+
name: String(prop["@_name"] ?? ""),
|
|
257
|
+
type: this.parseType((prop.type ?? prop.array) as Record<string, unknown> | undefined),
|
|
258
|
+
readable: prop["@_readable"] !== "0",
|
|
259
|
+
writable: prop["@_writable"] === "1",
|
|
260
|
+
constructOnly: prop["@_construct-only"] === "1",
|
|
261
|
+
hasDefault: prop["@_default-value"] !== undefined,
|
|
262
|
+
doc: extractDoc(prop),
|
|
263
|
+
}));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private parseSignals(signals: Record<string, unknown>[]): GirSignal[] {
|
|
267
|
+
if (!signals || !Array.isArray(signals)) {
|
|
268
|
+
return [];
|
|
269
|
+
}
|
|
270
|
+
return signals.map((signal) => {
|
|
271
|
+
const whenValue = String(signal["@_when"] ?? "last");
|
|
272
|
+
const validWhen = whenValue === "first" || whenValue === "last" || whenValue === "cleanup";
|
|
273
|
+
return {
|
|
274
|
+
name: String(signal["@_name"] ?? ""),
|
|
275
|
+
when: validWhen ? (whenValue as "first" | "last" | "cleanup") : "last",
|
|
276
|
+
returnType: signal["return-value"]
|
|
277
|
+
? this.parseReturnType(signal["return-value"] as Record<string, unknown>)
|
|
278
|
+
: undefined,
|
|
279
|
+
parameters:
|
|
280
|
+
signal.parameters && typeof signal.parameters === "object" && signal.parameters !== null
|
|
281
|
+
? this.parseParameters(signal.parameters as Record<string, unknown>)
|
|
282
|
+
: [],
|
|
283
|
+
doc: extractDoc(signal),
|
|
284
|
+
};
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private parseEnumerations(enumerations: Record<string, unknown>[]): GirEnumeration[] {
|
|
289
|
+
if (!enumerations || !Array.isArray(enumerations)) {
|
|
290
|
+
return [];
|
|
291
|
+
}
|
|
292
|
+
return enumerations.map((enumeration) => ({
|
|
293
|
+
name: String(enumeration["@_name"] ?? ""),
|
|
294
|
+
cType: String(enumeration["@_c:type"] ?? ""),
|
|
295
|
+
members: this.parseEnumerationMembers(
|
|
296
|
+
Array.isArray(enumeration.member) ? (enumeration.member as Record<string, unknown>[]) : [],
|
|
297
|
+
),
|
|
298
|
+
doc: extractDoc(enumeration),
|
|
299
|
+
}));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private parseEnumerationMembers(members: Record<string, unknown>[]): GirEnumerationMember[] {
|
|
303
|
+
if (!members || !Array.isArray(members)) {
|
|
304
|
+
return [];
|
|
305
|
+
}
|
|
306
|
+
return members.map((member) => ({
|
|
307
|
+
name: String(member["@_name"] ?? ""),
|
|
308
|
+
value: String(member["@_value"] ?? ""),
|
|
309
|
+
cIdentifier: String(member["@_c:identifier"] ?? ""),
|
|
310
|
+
doc: extractDoc(member),
|
|
311
|
+
}));
|
|
312
|
+
}
|
|
313
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a parsed GIR namespace containing all type definitions.
|
|
3
|
+
*/
|
|
4
|
+
export interface GirNamespace {
|
|
5
|
+
/** The namespace name (e.g., "Gtk", "Gio"). */
|
|
6
|
+
name: string;
|
|
7
|
+
/** The namespace version (e.g., "4.0"). */
|
|
8
|
+
version: string;
|
|
9
|
+
/** The shared library file name. */
|
|
10
|
+
sharedLibrary: string;
|
|
11
|
+
/** The C identifier prefix for this namespace. */
|
|
12
|
+
cPrefix: string;
|
|
13
|
+
/** All classes defined in this namespace. */
|
|
14
|
+
classes: GirClass[];
|
|
15
|
+
/** All interfaces defined in this namespace. */
|
|
16
|
+
interfaces: GirInterface[];
|
|
17
|
+
/** All standalone functions defined in this namespace. */
|
|
18
|
+
functions: GirFunction[];
|
|
19
|
+
/** All enumerations defined in this namespace. */
|
|
20
|
+
enumerations: GirEnumeration[];
|
|
21
|
+
/** All bitfield enumerations defined in this namespace. */
|
|
22
|
+
bitfields: GirEnumeration[];
|
|
23
|
+
/** Documentation for the namespace. */
|
|
24
|
+
doc?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Represents a GIR interface definition.
|
|
29
|
+
*/
|
|
30
|
+
export interface GirInterface {
|
|
31
|
+
/** The interface name. */
|
|
32
|
+
name: string;
|
|
33
|
+
/** The C type name. */
|
|
34
|
+
cType: string;
|
|
35
|
+
/** Methods defined on this interface. */
|
|
36
|
+
methods: GirMethod[];
|
|
37
|
+
/** Properties defined on this interface. */
|
|
38
|
+
properties: GirProperty[];
|
|
39
|
+
/** Signals defined on this interface. */
|
|
40
|
+
signals: GirSignal[];
|
|
41
|
+
/** Documentation for the interface. */
|
|
42
|
+
doc?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Represents a GIR class definition.
|
|
47
|
+
*/
|
|
48
|
+
export interface GirClass {
|
|
49
|
+
/** The class name. */
|
|
50
|
+
name: string;
|
|
51
|
+
/** The C type name. */
|
|
52
|
+
cType: string;
|
|
53
|
+
/** The parent class name, if any. */
|
|
54
|
+
parent?: string;
|
|
55
|
+
/** Whether this is an abstract class. */
|
|
56
|
+
abstract?: boolean;
|
|
57
|
+
/** List of interface names this class implements. */
|
|
58
|
+
implements: string[];
|
|
59
|
+
/** Methods defined on this class. */
|
|
60
|
+
methods: GirMethod[];
|
|
61
|
+
/** Constructor functions for this class. */
|
|
62
|
+
constructors: GirConstructor[];
|
|
63
|
+
/** Properties defined on this class. */
|
|
64
|
+
properties: GirProperty[];
|
|
65
|
+
/** Signals defined on this class. */
|
|
66
|
+
signals: GirSignal[];
|
|
67
|
+
/** Documentation for the class. */
|
|
68
|
+
doc?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Represents a GIR method definition.
|
|
73
|
+
*/
|
|
74
|
+
export interface GirMethod {
|
|
75
|
+
/** The method name. */
|
|
76
|
+
name: string;
|
|
77
|
+
/** The C function identifier. */
|
|
78
|
+
cIdentifier: string;
|
|
79
|
+
/** The return type. */
|
|
80
|
+
returnType: GirType;
|
|
81
|
+
/** The method parameters. */
|
|
82
|
+
parameters: GirParameter[];
|
|
83
|
+
/** Whether this method can throw a GError. */
|
|
84
|
+
throws?: boolean;
|
|
85
|
+
/** Documentation for the method. */
|
|
86
|
+
doc?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Represents a GIR constructor definition.
|
|
91
|
+
*/
|
|
92
|
+
export interface GirConstructor {
|
|
93
|
+
/** The constructor name. */
|
|
94
|
+
name: string;
|
|
95
|
+
/** The C function identifier. */
|
|
96
|
+
cIdentifier: string;
|
|
97
|
+
/** The return type (typically the class type). */
|
|
98
|
+
returnType: GirType;
|
|
99
|
+
/** The constructor parameters. */
|
|
100
|
+
parameters: GirParameter[];
|
|
101
|
+
/** Documentation for the constructor. */
|
|
102
|
+
doc?: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Represents a GIR standalone function definition.
|
|
107
|
+
*/
|
|
108
|
+
export interface GirFunction {
|
|
109
|
+
/** The function name. */
|
|
110
|
+
name: string;
|
|
111
|
+
/** The C function identifier. */
|
|
112
|
+
cIdentifier: string;
|
|
113
|
+
/** The return type. */
|
|
114
|
+
returnType: GirType;
|
|
115
|
+
/** The function parameters. */
|
|
116
|
+
parameters: GirParameter[];
|
|
117
|
+
/** Whether this function can throw a GError. */
|
|
118
|
+
throws?: boolean;
|
|
119
|
+
/** Documentation for the function. */
|
|
120
|
+
doc?: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Represents a GIR parameter definition.
|
|
125
|
+
*/
|
|
126
|
+
export interface GirParameter {
|
|
127
|
+
/** The parameter name. */
|
|
128
|
+
name: string;
|
|
129
|
+
/** The parameter type. */
|
|
130
|
+
type: GirType;
|
|
131
|
+
/** The parameter direction (in, out, or inout). */
|
|
132
|
+
direction?: "in" | "out" | "inout";
|
|
133
|
+
/** Whether this parameter can be null. */
|
|
134
|
+
nullable?: boolean;
|
|
135
|
+
/** Whether this parameter is optional. */
|
|
136
|
+
optional?: boolean;
|
|
137
|
+
/** Documentation for the parameter. */
|
|
138
|
+
doc?: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Represents a GIR type reference.
|
|
143
|
+
*/
|
|
144
|
+
export interface GirType {
|
|
145
|
+
/** The type name. */
|
|
146
|
+
name: string;
|
|
147
|
+
/** The C type name. */
|
|
148
|
+
cType?: string;
|
|
149
|
+
/** Whether this is an array type. */
|
|
150
|
+
isArray?: boolean;
|
|
151
|
+
/** The element type for array types. */
|
|
152
|
+
elementType?: GirType;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Represents a GIR property definition.
|
|
157
|
+
*/
|
|
158
|
+
export interface GirProperty {
|
|
159
|
+
/** The property name. */
|
|
160
|
+
name: string;
|
|
161
|
+
/** The property type. */
|
|
162
|
+
type: GirType;
|
|
163
|
+
/** Whether this property is readable. */
|
|
164
|
+
readable?: boolean;
|
|
165
|
+
/** Whether this property is writable. */
|
|
166
|
+
writable?: boolean;
|
|
167
|
+
/** Whether this property can only be set at construction time. */
|
|
168
|
+
constructOnly?: boolean;
|
|
169
|
+
/** Whether this property has a default value. */
|
|
170
|
+
hasDefault?: boolean;
|
|
171
|
+
/** Documentation for the property. */
|
|
172
|
+
doc?: string;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Represents a GIR signal definition.
|
|
177
|
+
*/
|
|
178
|
+
export interface GirSignal {
|
|
179
|
+
/** The signal name. */
|
|
180
|
+
name: string;
|
|
181
|
+
/** When the signal handler runs relative to the default handler. */
|
|
182
|
+
when?: "first" | "last" | "cleanup";
|
|
183
|
+
/** The signal return type. */
|
|
184
|
+
returnType?: GirType;
|
|
185
|
+
/** The signal parameters passed to handlers. */
|
|
186
|
+
parameters?: GirParameter[];
|
|
187
|
+
/** Documentation for the signal. */
|
|
188
|
+
doc?: string;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Represents a GIR enumeration or bitfield definition.
|
|
193
|
+
*/
|
|
194
|
+
export interface GirEnumeration {
|
|
195
|
+
/** The enumeration name. */
|
|
196
|
+
name: string;
|
|
197
|
+
/** The C type name. */
|
|
198
|
+
cType: string;
|
|
199
|
+
/** The enumeration members. */
|
|
200
|
+
members: GirEnumerationMember[];
|
|
201
|
+
/** Documentation for the enumeration. */
|
|
202
|
+
doc?: string;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Represents a single enumeration member.
|
|
207
|
+
*/
|
|
208
|
+
export interface GirEnumerationMember {
|
|
209
|
+
/** The member name. */
|
|
210
|
+
name: string;
|
|
211
|
+
/** The numeric value. */
|
|
212
|
+
value: string;
|
|
213
|
+
/** The C identifier. */
|
|
214
|
+
cIdentifier: string;
|
|
215
|
+
/** Documentation for the member. */
|
|
216
|
+
doc?: string;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Describes an FFI type for code generation.
|
|
221
|
+
*/
|
|
222
|
+
export interface FfiTypeDescriptor {
|
|
223
|
+
/** The FFI type category. */
|
|
224
|
+
type: string;
|
|
225
|
+
/** Size in bits for integer/float types. */
|
|
226
|
+
size?: number;
|
|
227
|
+
/** Whether the integer type is unsigned. */
|
|
228
|
+
unsigned?: boolean;
|
|
229
|
+
/** Whether the pointer is borrowed (not owned). */
|
|
230
|
+
borrowed?: boolean;
|
|
231
|
+
/** Inner type for ref types. */
|
|
232
|
+
innerType?: FfiTypeDescriptor;
|
|
233
|
+
/** Item type for array types. */
|
|
234
|
+
itemType?: FfiTypeDescriptor;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Converts a snake_case or kebab-case string to camelCase.
|
|
239
|
+
* @param str - The input string
|
|
240
|
+
* @returns The camelCase version
|
|
241
|
+
*/
|
|
242
|
+
export const toCamelCase = (str: string): string => str.replace(/[-_]([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Converts a snake_case or kebab-case string to PascalCase.
|
|
246
|
+
* @param str - The input string
|
|
247
|
+
* @returns The PascalCase version
|
|
248
|
+
*/
|
|
249
|
+
export const toPascalCase = (str: string): string => {
|
|
250
|
+
const camel = toCamelCase(str);
|
|
251
|
+
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Builds a map of class names to class definitions for quick lookup.
|
|
256
|
+
* @param classes - Array of GIR class definitions
|
|
257
|
+
* @returns Map from class name to class definition
|
|
258
|
+
*/
|
|
259
|
+
export const buildClassMap = (classes: GirClass[]): Map<string, GirClass> => {
|
|
260
|
+
const classMap = new Map<string, GirClass>();
|
|
261
|
+
for (const cls of classes) {
|
|
262
|
+
classMap.set(cls.name, cls);
|
|
263
|
+
}
|
|
264
|
+
return classMap;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Registers all enumerations and bitfields from a namespace with a type mapper.
|
|
269
|
+
* @param typeMapper - The TypeMapper instance to register with
|
|
270
|
+
* @param namespace - The GIR namespace containing enums to register
|
|
271
|
+
*/
|
|
272
|
+
export const registerEnumsFromNamespace = (typeMapper: TypeMapper, namespace: GirNamespace): void => {
|
|
273
|
+
for (const enumeration of namespace.enumerations) {
|
|
274
|
+
typeMapper.registerEnum(enumeration.name, toPascalCase(enumeration.name));
|
|
275
|
+
}
|
|
276
|
+
for (const bitfield of namespace.bitfields) {
|
|
277
|
+
typeMapper.registerEnum(bitfield.name, toPascalCase(bitfield.name));
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
type TypeMapping = { ts: string; ffi: FfiTypeDescriptor };
|
|
282
|
+
|
|
283
|
+
const BASIC_TYPE_MAP = new Map<string, TypeMapping>([
|
|
284
|
+
["gboolean", { ts: "boolean", ffi: { type: "boolean" } }],
|
|
285
|
+
["gchar", { ts: "number", ffi: { type: "int", size: 8, unsigned: false } }],
|
|
286
|
+
["guchar", { ts: "number", ffi: { type: "int", size: 8, unsigned: true } }],
|
|
287
|
+
["gint", { ts: "number", ffi: { type: "int", size: 32, unsigned: false } }],
|
|
288
|
+
["guint", { ts: "number", ffi: { type: "int", size: 32, unsigned: true } }],
|
|
289
|
+
["gshort", { ts: "number", ffi: { type: "int", size: 32, unsigned: false } }],
|
|
290
|
+
["gushort", { ts: "number", ffi: { type: "int", size: 32, unsigned: true } }],
|
|
291
|
+
["glong", { ts: "number", ffi: { type: "int", size: 64, unsigned: false } }],
|
|
292
|
+
["gulong", { ts: "number", ffi: { type: "int", size: 64, unsigned: true } }],
|
|
293
|
+
["GType", { ts: "number", ffi: { type: "int", size: 64, unsigned: true } }],
|
|
294
|
+
["gint8", { ts: "number", ffi: { type: "int", size: 8, unsigned: false } }],
|
|
295
|
+
["guint8", { ts: "number", ffi: { type: "int", size: 8, unsigned: true } }],
|
|
296
|
+
["gint16", { ts: "number", ffi: { type: "int", size: 32, unsigned: false } }],
|
|
297
|
+
["guint16", { ts: "number", ffi: { type: "int", size: 32, unsigned: true } }],
|
|
298
|
+
["gint32", { ts: "number", ffi: { type: "int", size: 32, unsigned: false } }],
|
|
299
|
+
["guint32", { ts: "number", ffi: { type: "int", size: 32, unsigned: true } }],
|
|
300
|
+
["gint64", { ts: "number", ffi: { type: "int", size: 64, unsigned: false } }],
|
|
301
|
+
["guint64", { ts: "number", ffi: { type: "int", size: 64, unsigned: true } }],
|
|
302
|
+
["gfloat", { ts: "number", ffi: { type: "float", size: 32 } }],
|
|
303
|
+
["gdouble", { ts: "number", ffi: { type: "float", size: 64 } }],
|
|
304
|
+
["utf8", { ts: "string", ffi: { type: "string" } }],
|
|
305
|
+
["filename", { ts: "string", ffi: { type: "string" } }],
|
|
306
|
+
["gpointer", { ts: "unknown", ffi: { type: "gobject" } }],
|
|
307
|
+
["gconstpointer", { ts: "unknown", ffi: { type: "gobject" } }],
|
|
308
|
+
["void", { ts: "void", ffi: { type: "undefined" } }],
|
|
309
|
+
["none", { ts: "void", ffi: { type: "undefined" } }],
|
|
310
|
+
["int", { ts: "number", ffi: { type: "int", size: 32, unsigned: false } }],
|
|
311
|
+
["uint", { ts: "number", ffi: { type: "int", size: 32, unsigned: true } }],
|
|
312
|
+
["long", { ts: "number", ffi: { type: "int", size: 64, unsigned: false } }],
|
|
313
|
+
["ulong", { ts: "number", ffi: { type: "int", size: 64, unsigned: true } }],
|
|
314
|
+
["size_t", { ts: "number", ffi: { type: "int", size: 64, unsigned: true } }],
|
|
315
|
+
["ssize_t", { ts: "number", ffi: { type: "int", size: 64, unsigned: false } }],
|
|
316
|
+
["double", { ts: "number", ffi: { type: "float", size: 64 } }],
|
|
317
|
+
["float", { ts: "number", ffi: { type: "float", size: 32 } }],
|
|
318
|
+
]);
|
|
319
|
+
|
|
320
|
+
const LIBRARY_MAP: Record<string, string> = {
|
|
321
|
+
Gtk: "libgtk-4.so.1",
|
|
322
|
+
GObject: "libgobject-2.0.so.0",
|
|
323
|
+
GLib: "libglib-2.0.so.0",
|
|
324
|
+
Gio: "libgio-2.0.so.0",
|
|
325
|
+
GdkPixbuf: "libgdk_pixbuf-2.0.so.0",
|
|
326
|
+
Pango: "libpango-1.0.so.0",
|
|
327
|
+
Cairo: "libcairo.so.2",
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Maps GIR types to TypeScript types and FFI type descriptors.
|
|
332
|
+
* Handles basic types, enumerations, arrays, and object references.
|
|
333
|
+
*/
|
|
334
|
+
export class TypeMapper {
|
|
335
|
+
private enumNames: Set<string> = new Set();
|
|
336
|
+
private enumTransforms: Map<string, string> = new Map();
|
|
337
|
+
private onEnumUsed?: (enumName: string) => void;
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Registers an enumeration type for mapping.
|
|
341
|
+
* @param originalName - The original GIR enum name
|
|
342
|
+
* @param transformedName - The transformed TypeScript enum name
|
|
343
|
+
*/
|
|
344
|
+
registerEnum(originalName: string, transformedName?: string): void {
|
|
345
|
+
this.enumNames.add(originalName);
|
|
346
|
+
if (transformedName) {
|
|
347
|
+
this.enumTransforms.set(originalName, transformedName);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Sets a callback to track enum usage during type mapping.
|
|
353
|
+
* @param callback - Called when an enum is used, or null to clear
|
|
354
|
+
*/
|
|
355
|
+
setEnumUsageCallback(callback: ((enumName: string) => void) | null): void {
|
|
356
|
+
this.onEnumUsed = callback ?? undefined;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Gets the current enum usage callback.
|
|
361
|
+
* @returns The callback or null if not set
|
|
362
|
+
*/
|
|
363
|
+
getEnumUsageCallback(): ((enumName: string) => void) | null {
|
|
364
|
+
return this.onEnumUsed ?? null;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Maps a GIR type to TypeScript and FFI type descriptors.
|
|
369
|
+
* @param girType - The GIR type to map
|
|
370
|
+
* @param isReturn - Whether this is a return type (affects pointer ownership)
|
|
371
|
+
* @returns The TypeScript type string and FFI descriptor
|
|
372
|
+
*/
|
|
373
|
+
mapType(girType: GirType, isReturn = false): TypeMapping {
|
|
374
|
+
if (girType.isArray || girType.name === "array") {
|
|
375
|
+
if (girType.elementType) {
|
|
376
|
+
const elementType = this.mapType(girType.elementType);
|
|
377
|
+
return {
|
|
378
|
+
ts: `${elementType.ts}[]`,
|
|
379
|
+
ffi: { type: "array", itemType: elementType.ffi },
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
ts: `unknown[]`,
|
|
384
|
+
ffi: { type: "array", itemType: { type: "undefined" } },
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const basicType = BASIC_TYPE_MAP.get(girType.name);
|
|
389
|
+
if (basicType) {
|
|
390
|
+
return basicType;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (this.enumNames.has(girType.name)) {
|
|
394
|
+
const transformedName = this.enumTransforms.get(girType.name) ?? girType.name;
|
|
395
|
+
this.onEnumUsed?.(transformedName);
|
|
396
|
+
return {
|
|
397
|
+
ts: transformedName,
|
|
398
|
+
ffi: { type: "int", size: 32, unsigned: false },
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (girType.name.includes(".")) {
|
|
403
|
+
const [_ns, typeName] = girType.name.split(".", 2);
|
|
404
|
+
if (typeName && this.enumNames.has(typeName)) {
|
|
405
|
+
const transformedName = this.enumTransforms.get(typeName) ?? typeName;
|
|
406
|
+
this.onEnumUsed?.(transformedName);
|
|
407
|
+
return {
|
|
408
|
+
ts: transformedName,
|
|
409
|
+
ffi: { type: "int", size: 32, unsigned: false },
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
ts: "unknown",
|
|
414
|
+
ffi: { type: "gobject", borrowed: isReturn },
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
ts: "unknown",
|
|
420
|
+
ffi: { type: "gobject", borrowed: isReturn },
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Maps a GIR parameter to TypeScript and FFI type descriptors.
|
|
426
|
+
* Handles out/inout parameters by wrapping in Ref type.
|
|
427
|
+
* @param param - The GIR parameter to map
|
|
428
|
+
* @returns The TypeScript type string and FFI descriptor
|
|
429
|
+
*/
|
|
430
|
+
mapParameter(param: GirParameter): TypeMapping {
|
|
431
|
+
if (param.direction === "out" || param.direction === "inout") {
|
|
432
|
+
const innerType = this.mapType(param.type);
|
|
433
|
+
return {
|
|
434
|
+
ts: `Ref<${innerType.ts}>`,
|
|
435
|
+
ffi: {
|
|
436
|
+
type: "ref",
|
|
437
|
+
innerType: innerType.ffi,
|
|
438
|
+
},
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (param.type.name === "GLib.Closure" || param.type.name.endsWith("Func")) {
|
|
443
|
+
return {
|
|
444
|
+
ts: "(...args: unknown[]) => unknown",
|
|
445
|
+
ffi: { type: "callback" },
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return this.mapType(param.type);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Checks if a parameter can accept null values.
|
|
454
|
+
* @param param - The parameter to check
|
|
455
|
+
* @returns True if the parameter is nullable or optional
|
|
456
|
+
*/
|
|
457
|
+
isNullable(param: GirParameter): boolean {
|
|
458
|
+
return param.nullable === true || param.optional === true;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Gets the shared library name for a namespace.
|
|
463
|
+
* @param namespace - The namespace name
|
|
464
|
+
* @returns The shared library file name
|
|
465
|
+
*/
|
|
466
|
+
getLibraryName(namespace: string): string {
|
|
467
|
+
return LIBRARY_MAP[namespace] ?? `lib${namespace.toLowerCase()}.so`;
|
|
468
|
+
}
|
|
469
|
+
}
|