-
-
Notifications
You must be signed in to change notification settings - Fork 25
/
gen_type_declarations.py
326 lines (243 loc) · 10 KB
/
gen_type_declarations.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
#!/usr/bin/env python
# This file generates the TypeScript declaration file
# this needs pyvips
#
# pip install --user pyvips
import argparse
import xml.etree.ElementTree as ET
from pyvips import Introspect, GValue, Error, \
ffi, enum_dict, flags_dict, \
gobject_lib, type_map, type_name, \
type_from_name, nickname_find
# turn a GType into a TypeScript type
gtype_to_js_param = {
GValue.gbool_type: 'boolean',
GValue.gint_type: 'number',
GValue.guint64_type: 'number',
GValue.gdouble_type: 'number',
GValue.gstr_type: 'string',
GValue.refstr_type: 'string',
GValue.gflags_type: 'Flag',
GValue.image_type: 'Image',
GValue.source_type: 'Source',
GValue.target_type: 'Target',
GValue.array_int_type: 'ArrayConstant',
GValue.array_double_type: 'ArrayConstant',
GValue.array_image_type: 'ArrayImage',
GValue.blob_type: 'Blob',
type_from_name('VipsInterpolate'): 'Interpolate'
}
gtype_to_js_return = {
GValue.gbool_type: 'boolean',
GValue.gint_type: 'number',
GValue.guint64_type: 'number',
GValue.gdouble_type: 'number',
GValue.gstr_type: 'string',
GValue.refstr_type: 'string',
GValue.gflags_type: 'number',
GValue.image_type: 'Image',
GValue.array_int_type: 'number[]',
GValue.array_double_type: 'number[]',
GValue.array_image_type: 'Vector<Image>',
GValue.blob_type: 'Uint8Array',
type_from_name('VipsAngle'): 'Angle'
}
js_keywords = ('in',)
# values for VipsArgumentFlags
_REQUIRED = 1
_INPUT = 16
_OUTPUT = 32
_DEPRECATED = 64
_MODIFY = 128
# for VipsOperationFlags
_OPERATION_DEPRECATED = 8
def get_js_type(gtype, param=False):
"""Map a gtype to JS type name we use to represent it.
"""
lookup = gtype_to_js_param if param else gtype_to_js_return
if gtype in lookup:
# we allow constants in image parameter types
if param and (gtype == GValue.image_type or
gtype == GValue.array_image_type):
return lookup[gtype] + ' | ArrayConstant'
return lookup[gtype]
fundamental = gobject_lib.g_type_fundamental(gtype)
if fundamental in lookup:
return lookup[fundamental]
return '<unknown type>'
def to_camel_case(snake_str):
components = snake_str.split('_')
# We capitalize the first letter of each component except the first one
# with the 'title' method and join them together.
return components[0] + ''.join(x.title() for x in components[1:])
# swap any '-' for '_'
def js_name(name):
name = name.replace('-', '_')
if name in js_keywords:
return '_' + name
return name
def remove_prefix(enum_str):
prefix = 'Vips'
if enum_str.startswith(prefix):
return enum_str[len(prefix):]
return enum_str
def generate_operation(operation_name, indent=' '):
intro = Introspect.get(operation_name)
required_output = [name for name in intro.required_output if name != intro.member_x]
has_input = len(intro.method_args) >= 1
has_output = len(required_output) >= 1
has_optional_options = len(intro.doc_optional_input) + len(intro.doc_optional_output) >= 1
# Add a comment block with some additional markings (@param, @return)
result = f'\n{indent}/**'
result += f'\n{indent} * {intro.description.capitalize()}.'
for name in intro.method_args:
result += f"\n{indent} * @param {js_name(name)} {intro.details[name]['blurb']}."
if has_optional_options:
result += f'\n{indent} * @param options Optional options.'
if has_output:
result += f"\n{indent} * @return {intro.details[required_output[0]]['blurb']}."
result += f'\n{indent} */\n'
if intro.member_x is None:
result += f'{indent}static '
else:
result += indent
js_operation = to_camel_case(operation_name)
result += f'{js_operation}('
result += ', '.join([f"{js_name(x)}: {get_js_type(intro.details[x]['type'], True)}"
for x in intro.method_args])
if has_optional_options:
if has_input:
result += ', '
result += 'options?: {'
for name in intro.doc_optional_input:
result += f'\n{indent} /**'
result += f"\n{indent} * {intro.details[name]['blurb'].capitalize()}."
result += f'\n{indent} */'
result += f"\n{indent} {js_name(name)}?: {get_js_type(intro.details[name]['type'], True)}"
for name in intro.doc_optional_output:
result += f'\n{indent} /**'
result += f"\n{indent} * {intro.details[name]['blurb'].capitalize()} (output)."
result += f'\n{indent} */'
result += f"\n{indent} {js_name(name)}?: {get_js_type(intro.details[name]['type'], False)} | undefined"
result += f'\n{indent}}}'
result += '): '
# the first output arg will be used as the result
if has_output:
js_type = get_js_type(intro.details[required_output[0]]['type'], False)
result += f'{js_type};'
else:
result += 'void;'
return result
def generate_type_declarations(filename, indent=' '):
all_nicknames = []
def add_nickname(gtype, a, b):
nickname = nickname_find(gtype)
try:
# can fail for abstract types
intro = Introspect.get(nickname)
# we are only interested in non-deprecated operations
if (intro.flags & _OPERATION_DEPRECATED) == 0:
all_nicknames.append(nickname)
except Error:
pass
type_map(gtype, add_nickname)
return ffi.NULL
type_map(type_from_name('VipsOperation'), add_nickname)
# add 'missing' synonyms by hand
all_nicknames.append('crop')
# make list unique and sort
all_nicknames = list(set(all_nicknames))
all_nicknames.sort(key=lambda x: (bool(Introspect.get(x).member_x), x))
# filter functions we document by hand
filter = ['composite', 'find_trim', 'profile', 'project',
'bandjoin_const', 'boolean_const', 'math2_const', 'relational_const', 'remainder_const']
all_nicknames = [name for name in all_nicknames if name not in filter]
with open(filename, 'a') as f:
f.write(f'{indent}//#region Auto-generated classes\n\n')
f.write(f'{indent}abstract class ImageAutoGen extends EmbindClassHandle<ImageAutoGen> {{\n')
f.write(f'{indent} // THIS IS A GENERATED CLASS. DO NOT EDIT DIRECTLY.\n')
for nickname in all_nicknames:
f.write(generate_operation(nickname) + '\n')
f.write(f'{indent}}}\n\n')
f.write(f'{indent}//#endregion\n\n')
f.write('}\n\n')
f.write('export = Vips;\n')
def generate_enums_flags(gir_file, out_file, indent=' '):
root = ET.parse(gir_file).getroot()
namespace = {
'goi': 'http://www.gtk.org/introspection/core/1.0'
}
# find all the enumerations/flags and make a dict for them
xml_enums = {}
for node in root.findall('goi:namespace/goi:enumeration', namespace):
xml_enums[node.get('name')] = node
xml_flags = {}
for node in root.findall('goi:namespace/goi:bitfield', namespace):
xml_flags[node.get('name')] = node
all_nicknames = []
def add_enum(gtype, a, b):
nickname = type_name(gtype)
all_nicknames.append(nickname)
gtype_to_js_param[gtype] = f'{remove_prefix(nickname)} | Enum'
type_map(gtype, add_enum)
return ffi.NULL
def add_flag(gtype, a, b):
nickname = type_name(gtype)
all_nicknames.append(nickname)
gtype_to_js_param[gtype] = f'{remove_prefix(nickname)} | Flag'
type_map(gtype, add_flag)
return ffi.NULL
type_map(type_from_name('GEnum'), add_enum)
type_map(type_from_name('GFlags'), add_flag)
# Filter internal flags
filter = ['VipsForeignFlags']
all_nicknames = [name for name in all_nicknames if name not in filter]
with open('preamble_vips.d.ts', 'r') as f:
preamble = f.read()
with open(out_file, 'w') as f:
f.write(preamble + '\n')
f.write(f'{indent}//#region Auto-generated enumerations\n\n')
for name in all_nicknames:
gtype = type_from_name(name)
name = remove_prefix(name)
if name in xml_enums:
node = xml_enums[name]
values = enum_dict(gtype)
elif name in xml_flags:
node = xml_flags[name]
values = flags_dict(gtype)
else:
continue
enum_doc = node.find('goi:doc', namespace)
if enum_doc is not None:
components = enum_doc.text.split('\n')
f.write(f'{indent}/**\n')
for text in components:
f.write(f"{indent} *{(' ' + text).rstrip()}\n")
f.write(f'{indent} */\n')
f.write(f'{indent}enum {name} {{\n')
for i, (key, value) in enumerate(values.items()):
js_key = key.replace('-', '_')
if i == 0 and (js_key == 'error' or js_key == 'notset'):
continue
member = node.find(f"goi:member[@name='{js_key}']", namespace)
member_doc = member.find('goi:doc', namespace)
if member_doc is not None:
text = member_doc.text[:1].upper() + member_doc.text[1:]
f.write(f'{indent} /**\n')
f.write(f'{indent} * {text}\n')
f.write(f'{indent} */\n')
f.write(f'{indent} {js_key} = {value}')
if i != len(values) - 1:
f.write(',')
f.write(f" // '{key}'\n")
f.write(f'{indent}}}\n\n')
f.write(f'{indent}//#endregion\n\n')
parser = argparse.ArgumentParser(description='TypeScript declaration generator')
parser.add_argument('gir',
type=argparse.FileType('r'),
help='GIR file')
if __name__ == '__main__':
args = parser.parse_args()
generate_enums_flags(args.gir, 'vips.d.ts')
generate_type_declarations('vips.d.ts')