Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/Scripts/protos/ProtoWrappers.py
1 views
1
#!/usr/bin/env python3
2
3
import os
4
import sys
5
import subprocess
6
import datetime
7
import argparse
8
import re
9
10
11
git_repo_path = os.path.abspath(
12
subprocess.check_output(["git", "rev-parse", "--show-toplevel"], text=True).strip()
13
)
14
15
enum_item_regex = re.compile(r"^(.+?)\s*=\s*(\d+?)\s*;$")
16
enum_regex = re.compile(r"^enum\s+(.+?)\s+\{$")
17
message_item_regex = re.compile(
18
r"^(optional|required|repeated)?\s*([\w\d\.]+?\s)\s*([\w\d]+?)\s*=\s*(\d+?)\s*(\[default = (.+)\])?;$"
19
)
20
message_regex = re.compile(r"^message\s+(.+?)\s+\{$")
21
multiline_comment_regex = re.compile(r"/\*.*?\*/", re.MULTILINE | re.DOTALL)
22
oneof_item_regex = re.compile(r"^(.+?)\s*([\w\d]+?)\s*=\s*(\d+?)\s*;$")
23
oneof_regex = re.compile(r"^oneof\s+(.+?)\s+\{$")
24
option_regex = re.compile(r"^option ")
25
package_regex = re.compile(r"^package\s+(.+);")
26
reserved_regex = re.compile(r"^reserved\s+(?:/*[^*]*\*/)?\s*\d+;$")
27
syntax_regex = re.compile(r'^syntax\s+=\s+"(.+)";')
28
validation_start_regex = re.compile(r"// MARK: - Begin Validation Logic for ([^ ]+) -")
29
30
proto_syntax = None
31
32
33
def lower_camel_case(name):
34
result = name
35
36
# We have at least two segments, we'll have to split them up
37
if "_" in name:
38
splits = name.split("_")
39
splits = [captialize_first_letter(split.lower()) for split in splits]
40
splits[0] = splits[0].lower()
41
result = "".join(splits)
42
43
# This name is all caps, lowercase it
44
elif name.isupper():
45
result = name.lower()
46
47
return supress_adjacent_capital_letters(result)
48
49
50
def camel_case(name):
51
result = name
52
53
splits = name.split("_")
54
splits = [captialize_first_letter(split) for split in splits]
55
result = "".join(splits)
56
57
return supress_adjacent_capital_letters(result)
58
59
60
def captialize_first_letter(name):
61
if name.isupper():
62
name = name.lower()
63
64
return name[0].upper() + name[1:]
65
66
67
# The generated code for "Apple Swift Protos" suppresses
68
# adjacent capital letters in lower_camel_case.
69
def supress_adjacent_capital_letters(name):
70
chars = []
71
lastWasUpper = False
72
for char in name:
73
if lastWasUpper:
74
char = char.lower()
75
chars.append(char)
76
lastWasUpper = char.isupper()
77
result = "".join(chars)
78
if result.endswith("Id"):
79
result = result[:-2] + "ID"
80
if result.endswith("IdBinary"):
81
result = result[:-8] + "IDBinary"
82
if result.endswith("Url"):
83
result = result[:-3] + "URL"
84
return result
85
86
87
def swift_type_for_proto_primitive_type(proto_type):
88
if proto_type == "string":
89
return "String"
90
elif proto_type == "uint64":
91
return "UInt64"
92
elif proto_type == "uint32":
93
return "UInt32"
94
elif proto_type == "fixed64":
95
return "UInt64"
96
elif proto_type == "int64":
97
return "Int64"
98
elif proto_type == "int32":
99
return "Int32"
100
elif proto_type == "bool":
101
return "Bool"
102
elif proto_type == "bytes":
103
return "Data"
104
elif proto_type == "double":
105
return "Double"
106
elif proto_type == "float":
107
return "Float"
108
else:
109
return None
110
111
112
def is_swift_primitive_type(proto_type):
113
return proto_type in (
114
"String",
115
"UInt64",
116
"UInt32",
117
"Int64",
118
"Int32",
119
"Bool",
120
"Data",
121
"Double",
122
"Float",
123
)
124
125
126
# Provides context for writing an indented block surrounded by braces.
127
#
128
# e.g.
129
#
130
# with BracedContext('class Foo', writer) as writer:
131
# with BracedContext('func bar() -> Bool', writer) as writer:
132
# return true
133
#
134
# Produces:
135
#
136
# class Foo {
137
# func bar() -> Bool {
138
# return true
139
# }
140
# }
141
#
142
class BracedContext:
143
def __init__(self, line, writer):
144
self.writer = writer
145
writer.add("%s {" % line)
146
147
def __enter__(self):
148
self.writer.push_indent()
149
return self.writer
150
151
def __exit__(self, *args):
152
self.writer.pop_indent()
153
self.writer.add("}")
154
155
156
class WriterContext:
157
def __init__(self, proto_name, swift_name, parent=None):
158
self.proto_name = proto_name
159
self.swift_name = swift_name
160
self.parent = parent
161
self.name_map = {}
162
163
164
class LineWriter:
165
def __init__(self, args):
166
self.contexts = []
167
# self.indent = 0
168
self.lines = []
169
self.args = args
170
self.current_indent = 0
171
172
def braced(self, line):
173
return BracedContext(line, self)
174
175
def push_indent(self):
176
self.current_indent = self.current_indent + 1
177
178
def pop_indent(self):
179
self.current_indent = self.current_indent - 1
180
if self.current_indent < 0:
181
raise Exception("Invalid indentation")
182
183
def all_context_proto_names(self):
184
return [context.proto_name for context in self.contexts]
185
186
def current_context(self):
187
return self.contexts[-1]
188
189
def indent(self):
190
return self.current_indent
191
# return len(self.contexts)
192
193
def push_context(self, proto_name, swift_name):
194
self.contexts.append(WriterContext(proto_name, swift_name))
195
self.push_indent()
196
197
def pop_context(self):
198
self.contexts.pop()
199
self.pop_indent()
200
201
def add(self, line):
202
self.lines.append((" " * self.indent()) + line)
203
204
def add_raw(self, line):
205
self.lines.append(line)
206
207
def extend(self, text):
208
for line in text.split("\n"):
209
self.add(line)
210
211
def join(self):
212
lines = [line.rstrip() for line in self.lines]
213
return "\n".join(lines)
214
215
def rstrip(self):
216
lines = self.lines
217
while len(lines) > 0 and len(lines[-1].strip()) == 0:
218
lines = lines[:-1]
219
self.lines = lines
220
221
def newline(self):
222
self.add("")
223
224
def needs_objc(self):
225
return proto_syntax == "proto2"
226
227
def add_objc(self):
228
if self.needs_objc():
229
self.add("@objc ")
230
231
232
class BaseContext(object):
233
def __init__(self):
234
self.parent = None
235
self.proto_name = None
236
237
def inherited_proto_names(self):
238
if self.parent is None:
239
return []
240
if self.proto_name is None:
241
return []
242
return self.parent.inherited_proto_names() + [
243
self.proto_name,
244
]
245
246
def derive_swift_name(self):
247
names = self.inherited_proto_names()
248
return self.args.wrapper_prefix + "".join(names)
249
250
def derive_wrapped_swift_name(self):
251
names = self.inherited_proto_names()
252
return self.args.proto_prefix + "_" + ".".join(names)
253
254
def qualified_proto_name(self):
255
names = self.inherited_proto_names()
256
return ".".join(names)
257
258
def children(self):
259
return []
260
261
def descendents(self):
262
result = []
263
for child in self.children():
264
result.append(child)
265
result.extend(child.descendents())
266
return result
267
268
def siblings(self):
269
result = []
270
if self.parent is not None:
271
result = self.parent.children()
272
return result
273
274
def ancestors(self):
275
result = []
276
if self.parent is not None:
277
result.append(self.parent)
278
result.extend(self.parent.ancestors())
279
return result
280
281
def context_for_proto_type(self, field):
282
should_deep_search = "." in field.proto_type
283
for candidate in self.all_known_contexts(should_deep_search=should_deep_search):
284
if candidate.proto_name == field.proto_type:
285
return candidate
286
if candidate.qualified_proto_name() == field.proto_type:
287
return candidate
288
if candidate.derive_swift_name() == field.proto_type:
289
return candidate
290
291
return None
292
293
def all_known_contexts(self, should_deep_search=False):
294
if should_deep_search:
295
root_ancestor = self.ancestors()[-1]
296
return root_ancestor.descendents()
297
298
candidates = []
299
candidates.extend(self.descendents())
300
candidates.extend(self.siblings())
301
for ancestor in self.ancestors():
302
if ancestor.proto_name is None:
303
# Ignore the root context
304
continue
305
candidates.append(ancestor)
306
candidates.extend(ancestor.siblings())
307
return candidates
308
309
def base_swift_type_for_field(self, field):
310
swift_type = swift_type_for_proto_primitive_type(field.proto_type)
311
if swift_type is not None:
312
return swift_type
313
else:
314
matching_context = self.context_for_proto_type(field)
315
if matching_context is not None:
316
return matching_context.swift_name
317
else:
318
# Failure
319
return field.proto_type
320
321
def swift_type_for_field(self, field, suppress_optional=False):
322
base_type = self.base_swift_type_for_field(field)
323
324
if field.rules == "optional" or field.rules == "explicit_proto3_optional":
325
if suppress_optional:
326
return base_type
327
can_be_optional = self.can_field_be_optional(field)
328
if can_be_optional:
329
return "%s?" % base_type
330
else:
331
return base_type
332
elif field.rules == "required":
333
return base_type
334
elif field.rules == "repeated":
335
return "[%s]" % base_type
336
else:
337
raise Exception("Unknown field type")
338
339
def is_field_primitive(self, field):
340
return field.proto_type in (
341
"uint64",
342
"uint32",
343
"fixed64",
344
"bool",
345
"double",
346
)
347
348
def can_field_be_optional(self, field):
349
return not self.is_field_primitive(field) or not field.is_required or field.rules == "explicit_proto3_optional"
350
351
def is_field_proto3_primitive(self, field):
352
return (
353
proto_syntax == "proto3"
354
and (self.is_field_primitive(field) or self.is_field_an_enum(field))
355
)
356
357
def is_field_an_enum(self, field):
358
matching_context = self.context_for_proto_type(field)
359
if matching_context is not None:
360
if type(matching_context) is EnumContext:
361
return True
362
return False
363
364
def is_field_oneof(self, field):
365
matching_context = self.context_for_proto_type(field)
366
if matching_context is not None:
367
if type(matching_context) is OneOfContext:
368
return True
369
return False
370
371
def is_field_a_proto(self, field):
372
matching_context = self.context_for_proto_type(field)
373
if matching_context is not None:
374
if type(matching_context) is MessageContext:
375
return True
376
return False
377
378
def is_field_a_proto_whose_init_throws(self, field):
379
matching_context = self.context_for_proto_type(field)
380
if matching_context is not None:
381
if type(matching_context) is MessageContext:
382
if matching_context.proto_name is self.proto_name:
383
for other_field in self.fields():
384
if other_field.index == field.index:
385
continue
386
elif self.context_for_proto_type(field).can_init_throw():
387
return True
388
return False
389
else:
390
return matching_context.can_init_throw()
391
return False
392
393
def can_field_be_optional_objc(self, field):
394
return (
395
self.can_field_be_optional(field)
396
and not self.is_field_primitive(field)
397
and not self.is_field_an_enum(field)
398
)
399
400
def default_value_for_field(self, field):
401
if field.rules == "repeated":
402
return "[]"
403
404
if field.default_value is not None and len(field.default_value) > 0:
405
return field.default_value
406
407
if field.rules == "optional" or field.rules == "explicit_proto3_optional":
408
can_be_optional = self.can_field_be_optional(field)
409
if can_be_optional:
410
return None # Swift provides this automatically.
411
412
if field.proto_type == "uint64":
413
return "0"
414
elif field.proto_type == "uint32":
415
return "0"
416
elif field.proto_type == "fixed64":
417
return "0"
418
elif field.proto_type == "double":
419
return "0"
420
elif field.proto_type == "bool":
421
return "false"
422
elif self.is_field_an_enum(field):
423
# TODO: Assert that rules is empty.
424
enum_context = self.context_for_proto_type(field)
425
return enum_context.default_value()
426
427
return None
428
429
430
class FileContext(BaseContext):
431
def __init__(self, args):
432
BaseContext.__init__(self)
433
434
self.args = args
435
436
self.messages = []
437
self.enums = []
438
439
def children(self):
440
return self.enums + self.messages
441
442
def prepare(self):
443
for child in self.children():
444
child.prepare()
445
446
def generate(self, writer):
447
writer.extend(
448
"""//
449
// Copyright 2022 Signal Messenger, LLC
450
// SPDX-License-Identifier: AGPL-3.0-only
451
//
452
453
import Foundation
454
public import SwiftProtobuf"""
455
)
456
457
writer.newline()
458
writer.extend(
459
"""
460
// WARNING: This code is generated. Only edit within the markers.
461
""".strip()
462
)
463
writer.newline()
464
465
writer.invalid_protobuf_error_name = "%sError" % self.args.wrapper_prefix
466
writer.extend(
467
(
468
"""
469
public enum %s: Error {
470
case invalidProtobuf(description: String)
471
}
472
"""
473
% writer.invalid_protobuf_error_name
474
).strip()
475
)
476
writer.newline()
477
478
for child in self.children():
479
child.generate(writer)
480
481
482
class MessageField:
483
def __init__(
484
self, name, index, rules, proto_type, default_value, sort_index, is_required
485
):
486
self.name = name
487
self.index = index
488
self.rules = rules
489
self.proto_type = proto_type
490
self.default_value = default_value
491
self.sort_index = sort_index
492
self.is_required = is_required
493
494
def has_accessor_name(self):
495
name = "has" + self.name_swift[0].upper() + self.name_swift[1:]
496
if name == "hasId":
497
# TODO: I'm not sure why "Apple Swift Proto" code formats the
498
# the name in this way.
499
name = "hasID"
500
elif name == "hasUrl":
501
# TODO: I'm not sure why "Apple Swift Proto" code formats the
502
# the name in this way.
503
name = "hasURL"
504
return name
505
506
507
class MessageContext(BaseContext):
508
def __init__(self, args, parent, proto_name):
509
BaseContext.__init__(self)
510
511
self.args = args
512
self.parent = parent
513
514
self.proto_name = proto_name
515
516
self.messages = []
517
self.enums = []
518
self.oneofs = []
519
520
self.field_map = {}
521
522
def fields(self):
523
fields = self.field_map.values()
524
fields = sorted(fields, key=lambda f: f.sort_index)
525
return fields
526
527
def field_indices(self):
528
return [field.index for field in self.fields()]
529
530
def field_names(self):
531
return [field.name for field in self.fields()]
532
533
def children(self):
534
return self.enums + self.messages + self.oneofs
535
536
def can_init_throw(self):
537
for field in self.fields():
538
if self.is_field_a_proto_whose_init_throws(field):
539
return True
540
if field.is_required and proto_syntax == "proto2":
541
return True
542
return False
543
544
def prepare(self):
545
self.swift_name = self.derive_swift_name()
546
self.swift_builder_name = "%sBuilder" % self.swift_name
547
548
for child in self.children():
549
child.prepare()
550
551
def generate(self, writer):
552
for child in self.messages:
553
child.generate(writer)
554
555
for child in self.enums:
556
child.generate(writer)
557
558
for child in self.oneofs:
559
child.generate(writer)
560
561
writer.add("// MARK: - %s" % self.swift_name)
562
writer.newline()
563
564
if writer.needs_objc():
565
writer.add_objc()
566
writer.add(
567
"public class %s: NSObject, Codable, NSSecureCoding {" % self.swift_name
568
)
569
else:
570
writer.add(
571
"public struct %s: Codable, CustomDebugStringConvertible {"
572
% self.swift_name
573
)
574
writer.newline()
575
576
writer.push_context(self.proto_name, self.swift_name)
577
578
wrapped_swift_name = self.derive_wrapped_swift_name()
579
580
# Prepare fields
581
explict_fields = []
582
implict_fields = []
583
for field in self.fields():
584
field.type_swift = self.swift_type_for_field(field)
585
field.type_swift_not_optional = self.swift_type_for_field(
586
field, suppress_optional=True
587
)
588
field.name_swift = lower_camel_case(field.name)
589
590
is_explicit = False
591
if field.is_required:
592
is_explicit = True
593
elif self.is_field_a_proto(field):
594
is_explicit = True
595
if is_explicit:
596
explict_fields.append(field)
597
else:
598
implict_fields.append(field)
599
600
# Ensure that no enum are required.
601
if (
602
proto_syntax == "proto2"
603
and self.is_field_an_enum(field)
604
and field.is_required
605
):
606
raise Exception(
607
"Enum fields cannot be required: %s.%s"
608
% (
609
self.proto_name,
610
field.name,
611
)
612
)
613
614
writer.add("fileprivate let proto: %s" % wrapped_swift_name)
615
writer.newline()
616
617
# Property Declarations
618
if len(explict_fields) > 0:
619
for field in explict_fields:
620
type_name = (
621
field.type_swift_not_optional
622
if field.is_required
623
else field.type_swift
624
)
625
writer.add_objc()
626
writer.add("public let %s: %s" % (field.name_swift, type_name))
627
628
if (
629
(not field.is_required)
630
and field.rules != "repeated"
631
and (not self.is_field_a_proto(field))
632
):
633
writer.add_objc()
634
writer.add("public var %s: Bool {" % field.has_accessor_name())
635
writer.push_indent()
636
writer.add("return proto.%s" % field.has_accessor_name())
637
writer.pop_indent()
638
writer.add("}")
639
writer.newline()
640
641
for field in implict_fields:
642
if field.rules == "optional" or field.rules == "explicit_proto3_optional":
643
can_be_optional = field.rules == "explicit_proto3_optional"
644
can_be_optional = can_be_optional or (not self.is_field_primitive(field) and not self.is_field_proto3_primitive(field))
645
if can_be_optional:
646
def write_field_getter(
647
is_objc_accessible, is_required_optional
648
):
649
if is_required_optional:
650
writer.add(
651
'// This "unwrapped" accessor should only be used if the "has value" accessor has already been checked.'
652
)
653
if is_objc_accessible:
654
writer.add_objc()
655
writer.add(
656
"public var unwrapped%s: %s {"
657
% (
658
camel_case(field.name_swift),
659
field.type_swift_not_optional,
660
)
661
)
662
writer.push_indent()
663
writer.add("if !%s {" % field.has_accessor_name())
664
writer.push_indent()
665
writer.add(
666
"// TODO: We could make this a crashing assert."
667
)
668
writer.add(
669
'owsFailDebug("Unsafe unwrap of missing optional: %s.%s.")'
670
% (
671
self.proto_name,
672
field.name_swift,
673
)
674
)
675
writer.pop_indent()
676
writer.add("}")
677
else:
678
if is_objc_accessible:
679
writer.add_objc()
680
writer.add(
681
"public var %s: %s? {"
682
% (
683
field.name_swift,
684
field.type_swift_not_optional,
685
)
686
)
687
writer.push_indent()
688
if not self.is_field_oneof(field):
689
writer.add(
690
"guard %s else {" % field.has_accessor_name()
691
)
692
writer.push_indent()
693
writer.add("return nil")
694
writer.pop_indent()
695
writer.add("}")
696
if self.is_field_an_enum(field):
697
enum_context = self.context_for_proto_type(field)
698
writer.add(
699
"return %s(proto.%s)"
700
% (
701
enum_context.wrap_func_name(),
702
field.name_swift,
703
)
704
)
705
elif self.is_field_oneof(field):
706
assert proto_syntax == "proto3"
707
oneof_context = self.context_for_proto_type(field)
708
writer.add(
709
"guard let %s = proto.%s else {"
710
% (
711
field.name_swift,
712
field.name_swift,
713
)
714
)
715
writer.push_indent()
716
writer.add("return nil")
717
writer.pop_indent()
718
writer.add("}")
719
if oneof_context.wrap_throws():
720
writer.add(
721
"guard let unwrapped%s = try? %s(%s) else {"
722
% (
723
camel_case(field.name_swift),
724
oneof_context.wrap_func_name(),
725
field.name_swift,
726
)
727
)
728
writer.push_indent()
729
writer.add(
730
'owsFailDebug("failed to unwrap %s")'
731
% field.name_swift
732
)
733
writer.add("return nil")
734
writer.pop_indent()
735
writer.add("}")
736
writer.add(
737
"return unwrapped%s" % camel_case(field.name_swift)
738
)
739
else:
740
writer.add("return %s(%s)" % (oneof_context.wrap_func_name(), field.name_swift))
741
else:
742
writer.add("return proto.%s" % field.name_swift)
743
writer.pop_indent()
744
writer.add("}")
745
746
if self.is_field_an_enum(field):
747
write_field_getter(
748
is_objc_accessible=False, is_required_optional=False
749
)
750
write_field_getter(
751
is_objc_accessible=True, is_required_optional=True
752
)
753
elif self.is_field_oneof(field):
754
write_field_getter(
755
is_objc_accessible=False, is_required_optional=False
756
)
757
else:
758
write_field_getter(
759
is_objc_accessible=True, is_required_optional=False
760
)
761
else:
762
writer.add_objc()
763
writer.add(
764
"public var %s: %s {"
765
% (field.name_swift, field.type_swift_not_optional)
766
)
767
writer.push_indent()
768
if self.is_field_an_enum(field):
769
enum_context = self.context_for_proto_type(field)
770
writer.add(
771
"return %s(proto.%s)"
772
% (
773
enum_context.wrap_func_name(),
774
field.name_swift,
775
)
776
)
777
else:
778
writer.add("return proto.%s" % field.name_swift)
779
writer.pop_indent()
780
writer.add("}")
781
782
if field.rules == "explicit_proto3_optional":
783
writer.add("public var %s: Bool {" % field.has_accessor_name())
784
writer.push_indent()
785
writer.add("return proto.%s" % field.has_accessor_name())
786
writer.pop_indent()
787
writer.add("}")
788
writer.newline()
789
elif not self.is_field_proto3_primitive(field) and not self.is_field_oneof(field):
790
writer.add_objc()
791
writer.add("public var %s: Bool {" % field.has_accessor_name())
792
writer.push_indent()
793
if proto_syntax == "proto3":
794
writer.add("return !proto.%s.isEmpty" % field.name_swift)
795
else:
796
writer.add("return proto.%s" % field.has_accessor_name())
797
writer.pop_indent()
798
writer.add("}")
799
writer.newline()
800
elif field.rules == "repeated":
801
writer.add_objc()
802
writer.add(
803
"public var %s: %s {"
804
% (field.name_swift, field.type_swift_not_optional)
805
)
806
writer.push_indent()
807
writer.add("return proto.%s" % field.name_swift)
808
writer.pop_indent()
809
writer.add("}")
810
writer.newline()
811
else:
812
writer.add_objc()
813
writer.add(
814
"public var %s: %s {"
815
% (field.name_swift, field.type_swift_not_optional)
816
)
817
writer.push_indent()
818
if self.is_field_an_enum(field):
819
enum_context = self.context_for_proto_type(field)
820
writer.add(
821
"return %s(proto.%s)"
822
% (
823
enum_context.unwrap_func_name(),
824
field.name_swift,
825
)
826
)
827
elif self.is_field_oneof(field):
828
oneof_context = self.context_for_proto_type(field)
829
writer.add(
830
"return %s(proto.%s)"
831
% (
832
oneof_context.unwrap_func_name(),
833
field.name_swift,
834
)
835
)
836
else:
837
writer.add("return proto.%s" % field.name_swift)
838
writer.pop_indent()
839
writer.add("}")
840
writer.newline()
841
842
# Unknown fields
843
writer.add("public var hasUnknownFields: Bool {")
844
writer.push_indent()
845
writer.add("return !proto.unknownFields.data.isEmpty")
846
writer.pop_indent()
847
writer.add("}")
848
849
writer.add("public var unknownFields: SwiftProtobuf.UnknownStorage? {")
850
writer.push_indent()
851
writer.add("guard hasUnknownFields else { return nil }")
852
writer.add("return proto.unknownFields")
853
writer.pop_indent()
854
writer.add("}")
855
writer.newline()
856
857
# Initializer
858
initializer_parameters = []
859
initializer_parameters.append("proto: %s" % wrapped_swift_name)
860
initializer_prefix = "private init("
861
for field in explict_fields:
862
type_name = (
863
field.type_swift_not_optional if field.is_required else field.type_swift
864
)
865
parameter = "%s: %s" % (field.name_swift, type_name)
866
parameter = "\n" + " " * len(initializer_prefix) + parameter
867
initializer_parameters.append(parameter)
868
initializer_parameters = ", ".join(initializer_parameters)
869
writer.extend(
870
"%s%s) {"
871
% (
872
initializer_prefix,
873
initializer_parameters,
874
)
875
)
876
writer.push_indent()
877
writer.add("self.proto = proto")
878
for field in explict_fields:
879
writer.add("self.%s = %s" % (field.name_swift, field.name_swift))
880
881
writer.pop_indent()
882
writer.add("}")
883
writer.newline()
884
885
# serializedData() func
886
writer.add_objc()
887
writer.extend(
888
(
889
"""
890
public func serializedData() throws -> Data {
891
return try self.proto.serializedData()
892
}
893
"""
894
).strip()
895
)
896
writer.newline()
897
898
# init(serializedData:) func
899
if writer.needs_objc():
900
writer.add_objc()
901
writer.add("public required convenience init(serializedData: Data) throws {")
902
else:
903
writer.add("public init(serializedData: Data) throws {")
904
writer.push_indent()
905
writer.add(
906
"let proto = try %s(serializedBytes: serializedData)" % (wrapped_swift_name,)
907
)
908
if self.can_init_throw():
909
writer.add("try self.init(proto)")
910
else:
911
writer.add("self.init(proto)")
912
writer.pop_indent()
913
writer.add("}")
914
writer.newline()
915
916
# init(proto:) func
917
chunks = ["fileprivate"]
918
if writer.needs_objc():
919
chunks.append("convenience")
920
chunks.append(f"init(_ proto: {wrapped_swift_name})")
921
if self.can_init_throw():
922
chunks.append("throws")
923
chunks.append("{")
924
writer.add(" ".join(chunks))
925
writer.push_indent()
926
927
for field in explict_fields:
928
if field.is_required:
929
930
if proto_syntax == "proto2":
931
writer.add("guard proto.%s else {" % field.has_accessor_name())
932
writer.push_indent()
933
writer.add(
934
'throw %s.invalidProtobuf(description: "[\\(Self.self)] missing required field: %s")'
935
% (
936
writer.invalid_protobuf_error_name,
937
field.name_swift,
938
)
939
)
940
writer.pop_indent()
941
writer.add("}")
942
943
if self.is_field_an_enum(field):
944
# TODO: Assert that rules is empty.
945
enum_context = self.context_for_proto_type(field)
946
writer.add(
947
"let %s = %s(proto.%s)"
948
% (
949
field.name_swift,
950
enum_context.wrap_func_name(),
951
field.name_swift,
952
)
953
)
954
elif self.is_field_a_proto_whose_init_throws(field):
955
writer.add(
956
"let %s = try %s(proto.%s)"
957
% (
958
field.name_swift,
959
self.base_swift_type_for_field(field),
960
field.name_swift,
961
)
962
),
963
elif self.is_field_a_proto(field):
964
writer.add(
965
"let %s = %s(proto.%s)"
966
% (
967
field.name_swift,
968
self.base_swift_type_for_field(field),
969
field.name_swift,
970
)
971
),
972
else:
973
writer.add(
974
"let %s = proto.%s"
975
% (
976
field.name_swift,
977
field.name_swift,
978
)
979
)
980
writer.newline()
981
continue
982
983
default_value = self.default_value_for_field(field)
984
if default_value is None:
985
writer.add("var %s: %s" % (field.name_swift, field.type_swift))
986
else:
987
writer.add(
988
"var %s: %s = %s"
989
% (field.name_swift, field.type_swift, default_value)
990
)
991
992
if field.rules == "repeated":
993
if self.is_field_an_enum(field):
994
enum_context = self.context_for_proto_type(field)
995
writer.add(
996
"%s = proto.%s.map { %s($0) }"
997
% (
998
field.name_swift,
999
field.name_swift,
1000
enum_context.wrap_func_name(),
1001
)
1002
)
1003
elif self.is_field_a_proto_whose_init_throws(field):
1004
writer.add(
1005
"%s = try proto.%s.map { try %s($0) }"
1006
% (
1007
field.name_swift,
1008
field.name_swift,
1009
self.base_swift_type_for_field(field),
1010
)
1011
)
1012
elif self.is_field_a_proto(field):
1013
writer.add(
1014
"%s = proto.%s.map { %s($0) }"
1015
% (
1016
field.name_swift,
1017
field.name_swift,
1018
self.base_swift_type_for_field(field),
1019
)
1020
)
1021
else:
1022
writer.add(
1023
"%s = proto.%s"
1024
% (
1025
field.name_swift,
1026
field.name_swift,
1027
)
1028
)
1029
else:
1030
writer.add("if proto.%s {" % field.has_accessor_name())
1031
writer.push_indent()
1032
1033
if self.is_field_an_enum(field):
1034
# TODO: Assert that rules is empty.
1035
enum_context = self.context_for_proto_type(field)
1036
writer.add(
1037
"%s = %s(proto.%s)"
1038
% (
1039
field.name_swift,
1040
enum_context.wrap_func_name(),
1041
field.name_swift,
1042
)
1043
)
1044
elif self.is_field_a_proto_whose_init_throws(field):
1045
writer.add(
1046
"%s = try %s(proto.%s)"
1047
% (
1048
field.name_swift,
1049
self.base_swift_type_for_field(field),
1050
field.name_swift,
1051
)
1052
),
1053
elif self.is_field_a_proto(field):
1054
writer.add(
1055
"%s = %s(proto.%s)"
1056
% (
1057
field.name_swift,
1058
self.base_swift_type_for_field(field),
1059
field.name_swift,
1060
)
1061
),
1062
else:
1063
writer.add(
1064
"%s = proto.%s"
1065
% (
1066
field.name_swift,
1067
field.name_swift,
1068
)
1069
)
1070
1071
writer.pop_indent()
1072
writer.add("}")
1073
writer.newline()
1074
1075
initializer_prefix = "self.init("
1076
initializer_arguments = []
1077
initializer_arguments.append("proto: proto")
1078
for field in explict_fields:
1079
argument = "%s: %s" % (field.name_swift, field.name_swift)
1080
argument = "\n" + " " * len(initializer_prefix) + argument
1081
initializer_arguments.append(argument)
1082
initializer_arguments = ", ".join(initializer_arguments)
1083
writer.extend(
1084
"%s%s)"
1085
% (
1086
initializer_prefix,
1087
initializer_arguments,
1088
)
1089
)
1090
writer.pop_indent()
1091
writer.add("}")
1092
writer.newline()
1093
1094
# codable
1095
1096
if writer.needs_objc():
1097
writer.add(
1098
"public required convenience init(from decoder: Swift.Decoder) throws {"
1099
)
1100
else:
1101
writer.add("public init(from decoder: Swift.Decoder) throws {")
1102
writer.push_indent()
1103
writer.add("let singleValueContainer = try decoder.singleValueContainer()")
1104
writer.add("let serializedData = try singleValueContainer.decode(Data.self)")
1105
writer.add("try self.init(serializedData: serializedData)")
1106
writer.pop_indent()
1107
writer.add("}")
1108
1109
writer.add("public func encode(to encoder: Swift.Encoder) throws {")
1110
writer.push_indent()
1111
writer.add("var singleValueContainer = encoder.singleValueContainer()")
1112
writer.add("try singleValueContainer.encode(try serializedData())")
1113
writer.pop_indent()
1114
writer.add("}")
1115
writer.newline()
1116
1117
# NSSecureCoding
1118
if writer.needs_objc():
1119
writer.add("public static var supportsSecureCoding: Bool { true }")
1120
writer.newline()
1121
1122
writer.add("public required convenience init?(coder: NSCoder) {")
1123
writer.push_indent()
1124
writer.add(
1125
"guard let serializedData = coder.decodeData() else { return nil }"
1126
)
1127
writer.add("do {")
1128
writer.push_indent()
1129
writer.add("try self.init(serializedData: serializedData)")
1130
writer.pop_indent()
1131
writer.add("} catch {")
1132
writer.push_indent()
1133
writer.add('owsFailDebug("Failed to decode serialized data \\(error)")')
1134
writer.add("return nil")
1135
writer.pop_indent()
1136
writer.add("}")
1137
writer.pop_indent()
1138
writer.add("}")
1139
writer.newline()
1140
1141
writer.add("public func encode(with coder: NSCoder) {")
1142
writer.push_indent()
1143
writer.add("do {")
1144
writer.push_indent()
1145
writer.add("coder.encode(try serializedData())")
1146
writer.pop_indent()
1147
writer.add("} catch {")
1148
writer.push_indent()
1149
writer.add('owsFailDebug("Failed to encode serialized data \\(error)")')
1150
writer.pop_indent()
1151
writer.add("}")
1152
writer.pop_indent()
1153
writer.add("}")
1154
writer.newline()
1155
1156
# description
1157
if writer.needs_objc():
1158
writer.add_objc()
1159
writer.add("public override var debugDescription: String {")
1160
else:
1161
writer.add("public var debugDescription: String {")
1162
writer.push_indent()
1163
writer.add('return "\\(proto)"')
1164
writer.pop_indent()
1165
writer.add("}")
1166
writer.newline()
1167
1168
writer.pop_context()
1169
1170
writer.rstrip()
1171
writer.add("}")
1172
writer.newline()
1173
self.generate_builder(writer)
1174
self.generate_debug_extension(writer)
1175
1176
def generate_debug_extension(self, writer):
1177
writer.add("#if TESTABLE_BUILD")
1178
writer.newline()
1179
with writer.braced("extension %s" % self.swift_name) as writer:
1180
writer.add_objc()
1181
with writer.braced(
1182
"public func serializedDataIgnoringErrors() -> Data?"
1183
) as writer:
1184
writer.add("return try! self.serializedData()")
1185
1186
writer.newline()
1187
1188
with writer.braced("extension %s" % self.swift_builder_name) as writer:
1189
writer.add_objc()
1190
with writer.braced(
1191
"public func buildIgnoringErrors() -> %s?" % self.swift_name
1192
) as writer:
1193
if self.can_init_throw():
1194
writer.add("return try! self.build()")
1195
else:
1196
writer.add("return self.buildInfallibly()")
1197
1198
writer.newline()
1199
writer.add("#endif")
1200
writer.newline()
1201
1202
def generate_builder(self, writer):
1203
1204
wrapped_swift_name = self.derive_wrapped_swift_name()
1205
1206
# Required Fields
1207
required_fields = [field for field in self.fields() if field.is_required]
1208
required_init_params = []
1209
required_init_args = []
1210
if len(required_fields) > 0:
1211
for field in required_fields:
1212
if field.rules == "repeated":
1213
param_type = "[" + self.base_swift_type_for_field(field) + "]"
1214
else:
1215
param_type = self.base_swift_type_for_field(field)
1216
required_init_params.append("%s: %s" % (field.name_swift, param_type))
1217
required_init_args.append(
1218
"%s: %s" % (field.name_swift, field.name_swift)
1219
)
1220
1221
with writer.braced("extension %s" % self.swift_name) as writer:
1222
# Convenience accessor.
1223
writer.add_objc()
1224
with writer.braced(
1225
"public static func builder(%s) -> %s"
1226
% (
1227
", ".join(required_init_params),
1228
self.swift_builder_name,
1229
)
1230
) as writer:
1231
writer.add(
1232
"return %s(%s)"
1233
% (
1234
self.swift_builder_name,
1235
", ".join(required_init_args),
1236
)
1237
)
1238
writer.newline()
1239
1240
# asBuilder()
1241
writer.add(
1242
"// asBuilder() constructs a builder that reflects the proto's contents."
1243
)
1244
writer.add_objc()
1245
with writer.braced(
1246
"public func asBuilder() -> %s" % (self.swift_builder_name,)
1247
) as writer:
1248
if writer.needs_objc():
1249
writer.add(
1250
"let builder = %s(%s)"
1251
% (
1252
self.swift_builder_name,
1253
", ".join(required_init_args),
1254
)
1255
)
1256
else:
1257
writer.add(
1258
"var builder = %s(%s)"
1259
% (
1260
self.swift_builder_name,
1261
", ".join(required_init_args),
1262
)
1263
)
1264
1265
for field in self.fields():
1266
if field.is_required:
1267
continue
1268
1269
accessor_name = field.name_swift
1270
accessor_name = "set" + accessor_name[0].upper() + accessor_name[1:]
1271
1272
if field.rules == "explicit_proto3_optional":
1273
writer.add("if let _value = %s {" % field.name_swift)
1274
writer.push_indent()
1275
writer.add("builder.%s(_value)" % (accessor_name,))
1276
writer.pop_indent()
1277
writer.add("}")
1278
elif field.rules == "repeated" or self.is_field_proto3_primitive(field):
1279
writer.add(
1280
"builder.%s(%s)"
1281
% (
1282
accessor_name,
1283
field.name_swift,
1284
)
1285
)
1286
elif not self.is_field_primitive(field):
1287
writer.add("if let _value = %s {" % field.name_swift)
1288
writer.push_indent()
1289
writer.add("builder.%s(_value)" % (accessor_name,))
1290
writer.pop_indent()
1291
writer.add("}")
1292
else:
1293
writer.add("if %s {" % field.has_accessor_name())
1294
writer.push_indent()
1295
writer.add(
1296
"builder.%s(%s)"
1297
% (
1298
accessor_name,
1299
field.name_swift,
1300
)
1301
)
1302
writer.pop_indent()
1303
writer.add("}")
1304
1305
writer.add("if let _value = unknownFields {")
1306
writer.push_indent()
1307
writer.add("builder.setUnknownFields(_value)")
1308
writer.pop_indent()
1309
writer.add("}")
1310
1311
writer.add("return builder")
1312
writer.newline()
1313
1314
if writer.needs_objc():
1315
writer.add_objc()
1316
writer.add("public class %s: NSObject {" % self.swift_builder_name)
1317
else:
1318
writer.add("public struct %s {" % self.swift_builder_name)
1319
writer.newline()
1320
1321
writer.push_context(self.proto_name, self.swift_name)
1322
1323
writer.add("private var proto = %s()" % wrapped_swift_name)
1324
writer.newline()
1325
1326
# Initializer
1327
if writer.needs_objc():
1328
writer.add_objc()
1329
writer.add("fileprivate override init() {}")
1330
else:
1331
writer.add("fileprivate init() {}")
1332
writer.newline()
1333
1334
# Required-Field Initializer
1335
if len(required_fields) > 0:
1336
# writer.add('// Initializer for required fields')
1337
writer.add_objc()
1338
writer.add("fileprivate init(%s) {" % ", ".join(required_init_params))
1339
writer.push_indent()
1340
if writer.needs_objc():
1341
writer.add("super.init()")
1342
writer.newline()
1343
for field in required_fields:
1344
accessor_name = field.name_swift
1345
accessor_name = "set" + accessor_name[0].upper() + accessor_name[1:]
1346
writer.add(
1347
"%s(%s)"
1348
% (
1349
accessor_name,
1350
field.name_swift,
1351
)
1352
)
1353
writer.pop_indent()
1354
writer.add("}")
1355
writer.newline()
1356
1357
# Setters
1358
for field in self.fields():
1359
if field.rules == "repeated":
1360
# Add
1361
accessor_name = field.name_swift
1362
accessor_name = "add" + accessor_name[0].upper() + accessor_name[1:]
1363
if writer.needs_objc():
1364
writer.add_objc()
1365
writer.add(
1366
"public func %s(_ valueParam: %s) {"
1367
% (
1368
accessor_name,
1369
self.base_swift_type_for_field(field),
1370
)
1371
)
1372
else:
1373
writer.add(
1374
"public mutating func %s(_ valueParam: %s) {"
1375
% (
1376
accessor_name,
1377
self.base_swift_type_for_field(field),
1378
)
1379
)
1380
writer.push_indent()
1381
if self.is_field_an_enum(field):
1382
enum_context = self.context_for_proto_type(field)
1383
param = "%s(valueParam)" % enum_context.unwrap_func_name()
1384
elif self.is_field_oneof(field):
1385
oneof_context = self.context_for_proto_type(field)
1386
param = "%s(valueParam)" % oneof_context.unwrap_func_name()
1387
elif self.is_field_a_proto(field):
1388
param = "valueParam.proto"
1389
else:
1390
param = "valueParam"
1391
writer.add("proto.%s.append(%s)" % (field.name_swift, param))
1392
writer.pop_indent()
1393
writer.add("}")
1394
writer.newline()
1395
1396
# Set
1397
accessor_name = field.name_swift
1398
accessor_name = "set" + accessor_name[0].upper() + accessor_name[1:]
1399
if writer.needs_objc():
1400
writer.add_objc()
1401
writer.add(
1402
"public func %s(_ wrappedItems: [%s]) {"
1403
% (
1404
accessor_name,
1405
self.base_swift_type_for_field(field),
1406
)
1407
)
1408
else:
1409
writer.add(
1410
"public mutating func %s(_ wrappedItems: [%s]) {"
1411
% (
1412
accessor_name,
1413
self.base_swift_type_for_field(field),
1414
)
1415
)
1416
writer.push_indent()
1417
if self.is_field_an_enum(field):
1418
enum_context = self.context_for_proto_type(field)
1419
writer.add(
1420
"proto.%s = wrappedItems.map { %s($0) }"
1421
% (
1422
field.name_swift,
1423
enum_context.unwrap_func_name(),
1424
)
1425
)
1426
elif self.is_field_a_proto(field):
1427
writer.add(
1428
"proto.%s = wrappedItems.map { $0.proto }" % (field.name_swift,)
1429
)
1430
else:
1431
writer.add("proto.%s = wrappedItems" % (field.name_swift,))
1432
writer.pop_indent()
1433
writer.add("}")
1434
writer.newline()
1435
else:
1436
accessor_name = field.name_swift
1437
accessor_name = "set" + accessor_name[0].upper() + accessor_name[1:]
1438
1439
# for fields that are supported as optionals in objc, we will add an objc only setter that takes an optional value
1440
can_field_be_optional_objc = self.can_field_be_optional_objc(field)
1441
if can_field_be_optional_objc:
1442
writer.add_objc()
1443
writer.add(
1444
"@available(swift, obsoleted: 1.0)"
1445
) # Don't allow using this function in Swift
1446
if writer.needs_objc():
1447
writer.add(
1448
"public func %s(_ valueParam: %s) {"
1449
% (accessor_name, self.swift_type_for_field(field))
1450
)
1451
else:
1452
writer.add(
1453
"public mutating func %s(_ valueParam: %s) {"
1454
% (accessor_name, self.swift_type_for_field(field))
1455
)
1456
writer.push_indent()
1457
writer.add("guard let valueParam = valueParam else { return }")
1458
1459
if self.is_field_an_enum(field):
1460
enum_context = self.context_for_proto_type(field)
1461
writer.add(
1462
"proto.%s = %s(valueParam)"
1463
% (
1464
field.name_swift,
1465
enum_context.unwrap_func_name(),
1466
)
1467
)
1468
elif self.is_field_oneof(field):
1469
oneof_context = self.context_for_proto_type(field)
1470
writer.add(
1471
"proto.%s = %s(valueParam)"
1472
% (
1473
field.name_swift,
1474
oneof_context.unwrap_func_name(),
1475
)
1476
)
1477
elif self.is_field_a_proto(field):
1478
writer.add("proto.%s = valueParam.proto" % (field.name_swift,))
1479
else:
1480
writer.add("proto.%s = valueParam" % (field.name_swift,))
1481
1482
writer.pop_indent()
1483
writer.add("}")
1484
writer.newline()
1485
1486
# Only allow the nonnull setter in objc if the field can't be optional
1487
if not can_field_be_optional_objc:
1488
writer.add_objc()
1489
if writer.needs_objc():
1490
writer.add(
1491
"public func %s(_ valueParam: %s) {"
1492
% (accessor_name, self.base_swift_type_for_field(field))
1493
)
1494
else:
1495
writer.add(
1496
"public mutating func %s(_ valueParam: %s) {"
1497
% (accessor_name, self.base_swift_type_for_field(field))
1498
)
1499
writer.push_indent()
1500
1501
if self.is_field_an_enum(field):
1502
enum_context = self.context_for_proto_type(field)
1503
writer.add(
1504
"proto.%s = %s(valueParam)"
1505
% (
1506
field.name_swift,
1507
enum_context.unwrap_func_name(),
1508
)
1509
)
1510
elif self.is_field_oneof(field):
1511
oneof_context = self.context_for_proto_type(field)
1512
writer.add(
1513
"proto.%s = %s(valueParam)"
1514
% (
1515
field.name_swift,
1516
oneof_context.unwrap_func_name(),
1517
)
1518
)
1519
elif self.is_field_a_proto(field):
1520
writer.add("proto.%s = valueParam.proto" % (field.name_swift,))
1521
else:
1522
writer.add("proto.%s = valueParam" % (field.name_swift,))
1523
1524
writer.pop_indent()
1525
writer.add("}")
1526
writer.newline()
1527
1528
# Unknown fields setter
1529
if writer.needs_objc():
1530
writer.add(
1531
"public func setUnknownFields(_ unknownFields: SwiftProtobuf.UnknownStorage) {"
1532
)
1533
else:
1534
writer.add(
1535
"public mutating func setUnknownFields(_ unknownFields: SwiftProtobuf.UnknownStorage) {"
1536
)
1537
writer.push_indent()
1538
writer.add("proto.unknownFields = unknownFields")
1539
writer.pop_indent()
1540
writer.add("}")
1541
writer.newline()
1542
1543
# build() func
1544
if self.can_init_throw():
1545
writer.add_objc()
1546
writer.add("public func build() throws -> %s {" % self.swift_name)
1547
writer.push_indent()
1548
writer.add("return try %s(proto)" % self.swift_name)
1549
writer.pop_indent()
1550
writer.add("}")
1551
writer.newline()
1552
else:
1553
writer.add_objc()
1554
writer.add("public func buildInfallibly() -> %s {" % self.swift_name)
1555
writer.push_indent()
1556
writer.add("return %s(proto)" % self.swift_name)
1557
writer.pop_indent()
1558
writer.add("}")
1559
writer.newline()
1560
1561
# buildSerializedData() func
1562
writer.add_objc()
1563
writer.add("public func buildSerializedData() throws -> Data {")
1564
writer.push_indent()
1565
writer.add("return try %s(proto).serializedData()" % self.swift_name)
1566
writer.pop_indent()
1567
writer.add("}")
1568
writer.newline()
1569
1570
writer.pop_context()
1571
1572
writer.rstrip()
1573
writer.add("}")
1574
writer.newline()
1575
1576
1577
class EnumContext(BaseContext):
1578
def __init__(self, args, parent, proto_name):
1579
BaseContext.__init__(self)
1580
1581
self.args = args
1582
self.parent = parent
1583
self.proto_name = proto_name
1584
1585
# self.item_names = set()
1586
# self.item_indices = set()
1587
self.item_map = {}
1588
1589
def derive_wrapped_swift_name(self):
1590
# return BaseContext.derive_wrapped_swift_name(self) + 'Enum'
1591
result = BaseContext.derive_wrapped_swift_name(self)
1592
if self.proto_name == "Type":
1593
result = result + "Enum"
1594
return result
1595
1596
def fully_qualify_wrappers(self):
1597
return False
1598
1599
def wrap_func_name(self):
1600
if self.fully_qualify_wrappers():
1601
return "%s.%sWrap" % (
1602
self.parent.swift_name,
1603
self.swift_name,
1604
)
1605
return "%sWrap" % (self.swift_name,)
1606
1607
def unwrap_func_name(self):
1608
if self.fully_qualify_wrappers():
1609
return "%s.%sUnwrap" % (
1610
self.parent.swift_name,
1611
self.swift_name,
1612
)
1613
return "%sUnwrap" % (self.swift_name,)
1614
1615
def item_names(self):
1616
return self.item_map.values()
1617
1618
def item_indices(self):
1619
return self.item_map.keys()
1620
1621
def prepare(self):
1622
self.swift_name = self.derive_swift_name()
1623
1624
for child in self.children():
1625
child.prepare()
1626
1627
def case_pairs(self):
1628
indices = [int(index) for index in self.item_indices()]
1629
indices = sorted(indices)
1630
result = []
1631
for index in indices:
1632
index_str = str(index)
1633
item_name = self.item_map[index_str]
1634
case_name = lower_camel_case(item_name)
1635
result.append(
1636
(
1637
case_name,
1638
index_str,
1639
)
1640
)
1641
return result
1642
1643
def default_value(self):
1644
for case_name, case_index in self.case_pairs():
1645
return "." + case_name
1646
1647
def generate(self, writer):
1648
1649
writer.add("// MARK: - %s" % self.swift_name)
1650
writer.newline()
1651
1652
if proto_syntax == "proto3":
1653
# proto3 enums are completely different.
1654
# Swift-only, with Int rawValue.
1655
writer.add("public enum %s: SwiftProtobuf.Enum {" % self.swift_name)
1656
1657
writer.push_context(self.proto_name, self.swift_name)
1658
1659
writer.add("public typealias RawValue = Int")
1660
1661
max_case_index = 0
1662
for case_name, case_index in self.case_pairs():
1663
if case_name in ["default", "true", "false"]:
1664
case_name = "`%s`" % case_name
1665
writer.add(
1666
"case %s // %s"
1667
% (
1668
case_name,
1669
case_index,
1670
)
1671
)
1672
max_case_index = max(max_case_index, int(case_index))
1673
1674
writer.add("case UNRECOGNIZED(Int)")
1675
1676
writer.newline()
1677
writer.add("public init() {")
1678
writer.push_indent()
1679
for case_name, case_index in self.case_pairs():
1680
writer.add("self = .%s" % case_name)
1681
break
1682
writer.pop_indent()
1683
writer.add("}")
1684
1685
writer.newline()
1686
writer.add("public init?(rawValue: Int) {")
1687
writer.push_indent()
1688
writer.add("switch rawValue {")
1689
writer.push_indent()
1690
for case_name, case_index in self.case_pairs():
1691
writer.add("case %s: self = .%s" % (case_index, case_name))
1692
writer.add("default: self = .UNRECOGNIZED(rawValue)")
1693
writer.pop_indent()
1694
writer.add("}")
1695
writer.pop_indent()
1696
writer.add("}")
1697
1698
writer.newline()
1699
writer.add("public var rawValue: Int {")
1700
writer.push_indent()
1701
writer.add("switch self {")
1702
writer.push_indent()
1703
for case_name, case_index in self.case_pairs():
1704
writer.add(
1705
"case .%s: return %s"
1706
% (
1707
case_name,
1708
case_index,
1709
)
1710
)
1711
writer.add("case .UNRECOGNIZED(let i): return i")
1712
writer.pop_indent()
1713
writer.add("}")
1714
writer.pop_indent()
1715
writer.add("}")
1716
1717
writer.pop_context()
1718
1719
writer.rstrip()
1720
writer.add("}")
1721
writer.newline()
1722
else:
1723
writer.add_objc()
1724
writer.add("public enum %s: Int32 {" % self.swift_name)
1725
1726
writer.push_context(self.proto_name, self.swift_name)
1727
1728
max_case_index = 0
1729
for case_name, case_index in self.case_pairs():
1730
if case_name in ["default", "true", "false"]:
1731
case_name = "`%s`" % case_name
1732
writer.add(
1733
"case %s = %s"
1734
% (
1735
case_name,
1736
case_index,
1737
)
1738
)
1739
max_case_index = max(max_case_index, int(case_index))
1740
1741
writer.pop_context()
1742
1743
writer.rstrip()
1744
writer.add("}")
1745
writer.newline()
1746
1747
wrapped_swift_name = self.derive_wrapped_swift_name()
1748
writer.add(
1749
"private func %sWrap(_ value: %s) -> %s {"
1750
% (
1751
self.swift_name,
1752
wrapped_swift_name,
1753
self.swift_name,
1754
)
1755
)
1756
writer.push_indent()
1757
writer.add("switch value {")
1758
for case_name, case_index in self.case_pairs():
1759
writer.add(
1760
"case .%s: return .%s"
1761
% (
1762
case_name,
1763
case_name,
1764
)
1765
)
1766
if proto_syntax == "proto3":
1767
writer.add("case .UNRECOGNIZED(let i): return .UNRECOGNIZED(i)")
1768
1769
writer.add("}")
1770
writer.pop_indent()
1771
writer.add("}")
1772
writer.newline()
1773
writer.add(
1774
"private func %sUnwrap(_ value: %s) -> %s {"
1775
% (
1776
self.swift_name,
1777
self.swift_name,
1778
wrapped_swift_name,
1779
)
1780
)
1781
writer.push_indent()
1782
writer.add("switch value {")
1783
for case_name, case_index in self.case_pairs():
1784
writer.add(
1785
"case .%s: return .%s"
1786
% (
1787
case_name,
1788
case_name,
1789
)
1790
)
1791
if proto_syntax == "proto3":
1792
writer.add("case .UNRECOGNIZED(let i): return .UNRECOGNIZED(i)")
1793
writer.add("}")
1794
writer.pop_indent()
1795
writer.add("}")
1796
writer.newline()
1797
1798
1799
class OneOfContext(BaseContext):
1800
def __init__(self, args, parent, proto_name):
1801
BaseContext.__init__(self)
1802
1803
self.args = args
1804
self.parent = parent
1805
self.proto_name = camel_case(proto_name)
1806
1807
self.item_type_map = {}
1808
self.item_index_map = {}
1809
1810
def derive_swift_name(self):
1811
names = self.inherited_proto_names()
1812
names.insert(-1, "OneOf")
1813
return self.args.wrapper_prefix + "".join(names)
1814
1815
def derive_wrapped_swift_name(self):
1816
names = self.inherited_proto_names()
1817
names[-1] = "OneOf_" + self.proto_name
1818
return self.args.proto_prefix + "_" + ".".join(names)
1819
1820
def qualified_proto_name(self):
1821
names = self.inherited_proto_names()
1822
names[-1] = "OneOf_" + self.proto_name
1823
return ".".join(names)
1824
1825
def item_names(self):
1826
return self.item_index_map.values()
1827
1828
def item_indices(self):
1829
return self.item_index_map.keys()
1830
1831
def sorted_item_indices(self):
1832
indices = [int(index) for index in self.item_indices()]
1833
return sorted(indices)
1834
1835
def last_index(self):
1836
return self.sorted_item_indices()[-1]
1837
1838
def wrap_func_name(self):
1839
return "%sWrap" % (self.swift_name,)
1840
1841
def unwrap_func_name(self):
1842
return "%sUnwrap" % (self.swift_name,)
1843
1844
def prepare(self):
1845
self.swift_name = self.derive_swift_name()
1846
1847
def context_for_proto_type(self, proto_type):
1848
for candidate in self.all_known_contexts(should_deep_search=False):
1849
if candidate.proto_name == proto_type:
1850
return candidate
1851
if candidate.qualified_proto_name() == proto_type:
1852
return candidate
1853
1854
return None
1855
1856
def case_tuples(self):
1857
result = []
1858
for index in self.sorted_item_indices():
1859
index_str = str(index)
1860
item_name = self.item_index_map[index_str]
1861
item_type = self.item_type_map[item_name]
1862
case_name = lower_camel_case(item_name)
1863
case_type = swift_type_for_proto_primitive_type(item_type)
1864
case_throws = False
1865
if case_type is None:
1866
case_type = self.context_for_proto_type(item_type).swift_name
1867
case_throws = self.is_field_a_proto_whose_init_throws(item_type)
1868
result.append((case_name, case_type, case_throws))
1869
return result
1870
1871
def wrap_throws(self):
1872
return any(case_throws for _, _, case_throws in self.case_tuples())
1873
1874
def generate(self, writer):
1875
1876
writer.add("// MARK: - %s" % self.swift_name)
1877
writer.newline()
1878
1879
# proto3 enums are completely different.
1880
# Swift-only, with Int rawValue.
1881
writer.add("public enum %s {" % self.swift_name)
1882
1883
writer.push_context(self.proto_name, self.swift_name)
1884
1885
for case_name, case_type, _ in self.case_tuples():
1886
writer.add(
1887
"case %s(%s)"
1888
% (
1889
case_name,
1890
case_type,
1891
)
1892
)
1893
1894
writer.pop_context()
1895
1896
writer.rstrip()
1897
writer.add("}")
1898
writer.newline()
1899
1900
wrapped_swift_name = self.derive_wrapped_swift_name()
1901
writer.add(
1902
"private func %sWrap(_ value: %s)%s -> %s {"
1903
% (
1904
self.swift_name,
1905
wrapped_swift_name,
1906
" throws" if self.wrap_throws() else "",
1907
self.swift_name,
1908
)
1909
)
1910
writer.push_indent()
1911
writer.add("switch value {")
1912
for case_name, case_type, case_throws in self.case_tuples():
1913
if is_swift_primitive_type(case_type):
1914
writer.add(
1915
"case .%s(let value): return .%s(value)"
1916
% (
1917
case_name,
1918
case_name,
1919
)
1920
)
1921
elif case_throws:
1922
writer.add(
1923
"case .%s(let value): return .%s(try %s(value))"
1924
% (
1925
case_name,
1926
case_name,
1927
case_type,
1928
)
1929
)
1930
else:
1931
writer.add(
1932
"case .%s(let value): return .%s(%s(value))"
1933
% (
1934
case_name,
1935
case_name,
1936
case_type,
1937
)
1938
)
1939
1940
writer.add("}")
1941
writer.pop_indent()
1942
writer.add("}")
1943
writer.newline()
1944
1945
writer.add(
1946
"private func %sUnwrap(_ value: %s) -> %s {"
1947
% (
1948
self.swift_name,
1949
self.swift_name,
1950
wrapped_swift_name,
1951
)
1952
)
1953
writer.push_indent()
1954
writer.add("switch value {")
1955
for case_name, case_type, case_throws in self.case_tuples():
1956
if is_swift_primitive_type(case_type):
1957
writer.add(
1958
"case .%s(let value): return .%s(value)"
1959
% (
1960
case_name,
1961
case_name,
1962
)
1963
)
1964
else:
1965
writer.add(
1966
"case .%s(let value): return .%s(value.proto)"
1967
% (
1968
case_name,
1969
case_name,
1970
)
1971
)
1972
writer.add("}")
1973
writer.pop_indent()
1974
writer.add("}")
1975
writer.newline()
1976
1977
1978
class LineParser:
1979
def __init__(self, text):
1980
self.lines = text.split("\n")
1981
self.lines.reverse()
1982
self.next_line_comments = []
1983
1984
def __next__(self):
1985
# lineParser = LineParser(text.split('\n'))
1986
1987
self.next_line_comments = []
1988
while len(self.lines) > 0:
1989
line = self.lines.pop()
1990
line = line.strip()
1991
# if not line:
1992
# continue
1993
1994
comment_index = line.find("//")
1995
if comment_index >= 0:
1996
comment = line[comment_index + len("//") :].strip()
1997
line = line[:comment_index].strip()
1998
if not line:
1999
if comment:
2000
self.next_line_comments.append(comment)
2001
else:
2002
if not line:
2003
self.next_line_comments = []
2004
2005
if not line:
2006
continue
2007
2008
# if args.verbose:
2009
# print 'line:', line
2010
2011
return line
2012
raise StopIteration()
2013
2014
2015
def parse_enum(args, proto_file_path, parser, parent_context, enum_name):
2016
2017
# if args.verbose:
2018
# print '# enum:', enum_name
2019
2020
context = EnumContext(args, parent_context, enum_name)
2021
2022
allow_alias = False
2023
while True:
2024
try:
2025
line = next(parser)
2026
except StopIteration:
2027
raise Exception("Incomplete enum: %s" % proto_file_path)
2028
2029
if line == "option allow_alias = true;":
2030
allow_alias = True
2031
continue
2032
2033
if line == "}":
2034
# if args.verbose:
2035
# print
2036
parent_context.enums.append(context)
2037
return
2038
2039
if reserved_regex.search(line):
2040
continue
2041
2042
item_match = enum_item_regex.search(line)
2043
if item_match:
2044
item_name = item_match.group(1).strip()
2045
item_index = item_match.group(2).strip()
2046
2047
# if args.verbose:
2048
# print '\t enum item[%s]: %s' % (item_index, item_name)
2049
2050
if item_name in context.item_names():
2051
raise Exception(
2052
"Duplicate enum name[%s]: %s" % (proto_file_path, item_name)
2053
)
2054
2055
if item_index in context.item_indices():
2056
if allow_alias:
2057
continue
2058
raise Exception(
2059
"Duplicate enum index[%s]: %s" % (proto_file_path, item_name)
2060
)
2061
2062
context.item_map[item_index] = item_name
2063
2064
continue
2065
2066
raise Exception('Invalid enum syntax[%s]: "%s"' % (proto_file_path, line))
2067
2068
2069
def parse_oneof(args, proto_file_path, parser, parent_context, oneof_name):
2070
2071
# if args.verbose:
2072
# print '# oneof:', oneof_name
2073
2074
if oneof_name in parent_context.field_names():
2075
raise Exception(
2076
"Duplicate message field name[%s]: %s" % (proto_file_path, oneof_name)
2077
)
2078
2079
context = OneOfContext(args, parent_context, oneof_name)
2080
2081
oneof_index = None
2082
2083
while True:
2084
try:
2085
line = next(parser)
2086
except StopIteration:
2087
raise Exception("Incomplete oneof: %s" % proto_file_path)
2088
2089
if line == "}":
2090
break
2091
2092
item_match = oneof_item_regex.search(line)
2093
if item_match:
2094
item_type = item_match.group(1).strip()
2095
item_name = item_match.group(2).strip()
2096
item_index = item_match.group(3).strip()
2097
2098
# if args.verbose:
2099
# print '\t oneof item[%s]: %s' % (item_index, item_name)
2100
2101
if item_name in context.item_names():
2102
raise Exception(
2103
"Duplicate oneof name[%s]: %s" % (proto_file_path, item_name)
2104
)
2105
2106
if item_index in context.item_indices():
2107
raise Exception(
2108
"Duplicate oneof index[%s]: %s" % (proto_file_path, item_name)
2109
)
2110
2111
context.item_type_map[item_name] = item_type
2112
context.item_index_map[item_index] = item_name
2113
2114
continue
2115
2116
raise Exception('Invalid oneof syntax[%s]: "%s"' % (proto_file_path, line))
2117
2118
parent_context.oneofs.append(context)
2119
return context
2120
2121
2122
def optional_match_group(match, index):
2123
group = match.group(index)
2124
if group is None:
2125
return None
2126
return group.strip()
2127
2128
2129
def parse_message(args, proto_file_path, parser, parent_context, message_name):
2130
2131
# if args.verbose:
2132
# print '# message:', message_name
2133
2134
context = MessageContext(args, parent_context, message_name)
2135
2136
sort_index = 0
2137
while True:
2138
try:
2139
line = next(parser)
2140
except StopIteration:
2141
raise Exception("Incomplete message: %s" % proto_file_path)
2142
2143
field_comments = parser.next_line_comments
2144
2145
if line == "}":
2146
# if args.verbose:
2147
# print
2148
parent_context.messages.append(context)
2149
return
2150
2151
enum_match = enum_regex.search(line)
2152
if enum_match:
2153
enum_name = enum_match.group(1).strip()
2154
parse_enum(args, proto_file_path, parser, context, enum_name)
2155
continue
2156
2157
message_match = message_regex.search(line)
2158
if message_match:
2159
message_name = message_match.group(1).strip()
2160
parse_message(args, proto_file_path, parser, context, message_name)
2161
continue
2162
2163
if proto_syntax == "proto3":
2164
oneof_match = oneof_regex.search(line)
2165
if oneof_match:
2166
oneof_name = oneof_match.group(1).strip()
2167
oneof_context = parse_oneof(
2168
args, proto_file_path, parser, context, oneof_name
2169
)
2170
oneof_index = oneof_context.last_index()
2171
oneof_type = oneof_context.derive_swift_name()
2172
context.field_map[oneof_index] = MessageField(
2173
oneof_name,
2174
oneof_index,
2175
"optional",
2176
oneof_type,
2177
None,
2178
sort_index,
2179
False,
2180
)
2181
sort_index = sort_index + 1
2182
continue
2183
2184
if reserved_regex.search(line):
2185
continue
2186
2187
# Examples:
2188
#
2189
# optional bytes id = 1;
2190
# optional bool isComplete = 2 [default = false];
2191
#
2192
# NOTE: required is not valid in proto3.
2193
item_match = message_item_regex.search(line)
2194
if item_match:
2195
# print 'item_rules:', item_match.groups()
2196
item_rules = optional_match_group(item_match, 1)
2197
item_type = optional_match_group(item_match, 2)
2198
item_name = optional_match_group(item_match, 3)
2199
item_index = optional_match_group(item_match, 4)
2200
# item_defaults_1 = optional_match_group(item_match, 5)
2201
item_default = optional_match_group(item_match, 6)
2202
2203
if proto_syntax == "proto3":
2204
if item_rules is None:
2205
item_rules = "optional"
2206
elif item_rules == "optional":
2207
item_rules = "explicit_proto3_optional"
2208
elif item_rules == "repeated":
2209
pass
2210
else:
2211
raise Exception(
2212
"Unexpected rule[%s]: %s" % (proto_file_path, item_rules)
2213
)
2214
2215
# print 'item_rules:', item_rules
2216
# print 'item_type:', item_type
2217
# print 'item_name:', item_name
2218
# print 'item_index:', item_index
2219
# print 'item_default:', item_default
2220
2221
message_field = {
2222
"rules": item_rules,
2223
"type": item_type,
2224
"name": item_name,
2225
"index": item_index,
2226
"default": item_default,
2227
"field_comments": field_comments,
2228
}
2229
# print 'message_field:', message_field
2230
2231
# if args.verbose:
2232
# print '\t message field[%s]: %s' % (item_index, str(message_field))
2233
2234
if item_name in context.field_names():
2235
raise Exception(
2236
"Duplicate message field name[%s]: %s"
2237
% (proto_file_path, item_name)
2238
)
2239
# context.field_names.add(item_name)
2240
2241
if item_index in context.field_indices():
2242
raise Exception(
2243
"Duplicate message field index[%s]: %s"
2244
% (proto_file_path, item_name)
2245
)
2246
# context.field_indices.add(item_index)
2247
2248
is_required = "@required" in field_comments
2249
# if is_required:
2250
# print 'is_required:', item_name
2251
# print 'item_name:', item_name, 'item_type:', item_type
2252
2253
context.field_map[item_index] = MessageField(
2254
item_name,
2255
item_index,
2256
item_rules,
2257
item_type,
2258
item_default,
2259
sort_index,
2260
is_required,
2261
)
2262
2263
sort_index = sort_index + 1
2264
2265
continue
2266
2267
raise Exception("Invalid message syntax[%s]: %s" % (proto_file_path, line))
2268
2269
2270
def process_proto_file(args, proto_file_path, dst_file_path):
2271
with open(proto_file_path, "rt") as f:
2272
text = f.read()
2273
2274
text = multiline_comment_regex.sub("", text)
2275
2276
parser = LineParser(text)
2277
2278
# lineParser = LineParser(text.split('\n'))
2279
2280
context = FileContext(args)
2281
2282
while True:
2283
try:
2284
line = next(parser)
2285
except StopIteration:
2286
break
2287
2288
enum_match = enum_regex.search(line)
2289
if enum_match:
2290
enum_name = enum_match.group(1).strip()
2291
parse_enum(args, proto_file_path, parser, context, enum_name)
2292
continue
2293
2294
syntax_match = syntax_regex.search(line)
2295
if syntax_match:
2296
global proto_syntax
2297
proto_syntax = syntax_match.group(1).strip()
2298
if args.verbose:
2299
print("Syntax:", proto_syntax)
2300
continue
2301
2302
if option_regex.search(line):
2303
if args.verbose:
2304
print("# Ignoring option")
2305
continue
2306
2307
package_match = package_regex.search(line)
2308
if package_match:
2309
if args.package:
2310
raise Exception("More than one package statement: %s" % proto_file_path)
2311
args.package = package_match.group(1).strip()
2312
2313
if args.verbose:
2314
print("# package:", args.package)
2315
continue
2316
2317
message_match = message_regex.search(line)
2318
if message_match:
2319
message_name = message_match.group(1).strip()
2320
parse_message(args, proto_file_path, parser, context, message_name)
2321
continue
2322
2323
raise Exception("Invalid syntax[%s]: %s" % (proto_file_path, line))
2324
2325
writer = LineWriter(args)
2326
context.prepare()
2327
context.generate(writer)
2328
output = writer.join()
2329
with open(dst_file_path, "wt") as f:
2330
f.write(output)
2331
2332
2333
if __name__ == "__main__":
2334
2335
parser = argparse.ArgumentParser(
2336
description="Protocol Buffer Swift Wrapper Generator."
2337
)
2338
parser.add_argument("--proto-dir", help="dir path of the proto schema file.")
2339
parser.add_argument("--proto-file", help="filename of the proto schema file.")
2340
parser.add_argument("--wrapper-prefix", help="name prefix for generated wrappers.")
2341
parser.add_argument("--proto-prefix", help="name prefix for proto bufs.")
2342
parser.add_argument("--dst-dir", help="path to the destination directory.")
2343
parser.add_argument(
2344
"--verbose", action="store_true", help="enables verbose logging"
2345
)
2346
args = parser.parse_args()
2347
2348
if args.verbose:
2349
print("args:", args)
2350
2351
proto_file_path = os.path.abspath(os.path.join(args.proto_dir, args.proto_file))
2352
if not os.path.exists(proto_file_path):
2353
raise Exception("File does not exist: %s" % proto_file_path)
2354
2355
dst_dir_path = os.path.abspath(args.dst_dir)
2356
if not os.path.exists(dst_dir_path):
2357
raise Exception("Destination does not exist: %s" % dst_dir_path)
2358
2359
dst_file_path = os.path.join(dst_dir_path, "%s.swift" % args.wrapper_prefix)
2360
2361
if args.verbose:
2362
print("dst_file_path:", dst_file_path)
2363
2364
args.package = None
2365
process_proto_file(args, proto_file_path, dst_file_path)
2366
2367
# print 'complete.'
2368
2369