2350 lines
79 KiB
Python
Executable file
2350 lines
79 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
import datetime
|
|
import argparse
|
|
import re
|
|
|
|
|
|
git_repo_path = os.path.abspath(
|
|
subprocess.check_output(["git", "rev-parse", "--show-toplevel"], text=True).strip()
|
|
)
|
|
|
|
enum_item_regex = re.compile(r"^(.+?)\s*=\s*(\d+?)\s*;$")
|
|
enum_regex = re.compile(r"^enum\s+(.+?)\s+\{$")
|
|
message_item_regex = re.compile(
|
|
r"^(optional|required|repeated)?\s*([\w\d\.]+?\s)\s*([\w\d]+?)\s*=\s*(\d+?)\s*(\[default = (.+)\])?;$"
|
|
)
|
|
message_regex = re.compile(r"^message\s+(.+?)\s+\{$")
|
|
multiline_comment_regex = re.compile(r"/\*.*?\*/", re.MULTILINE | re.DOTALL)
|
|
oneof_item_regex = re.compile(r"^(.+?)\s*([\w\d]+?)\s*=\s*(\d+?)\s*;$")
|
|
oneof_regex = re.compile(r"^oneof\s+(.+?)\s+\{$")
|
|
option_regex = re.compile(r"^option ")
|
|
package_regex = re.compile(r"^package\s+(.+);")
|
|
reserved_regex = re.compile(r"^reserved\s+(?:/*[^*]*\*/)?\s*\d+;$")
|
|
syntax_regex = re.compile(r'^syntax\s+=\s+"(.+)";')
|
|
validation_start_regex = re.compile(r"// MARK: - Begin Validation Logic for ([^ ]+) -")
|
|
|
|
proto_syntax = None
|
|
|
|
|
|
def lower_camel_case(name):
|
|
result = name
|
|
|
|
# We have at least two segments, we'll have to split them up
|
|
if "_" in name:
|
|
splits = name.split("_")
|
|
splits = [captialize_first_letter(split.lower()) for split in splits]
|
|
splits[0] = splits[0].lower()
|
|
result = "".join(splits)
|
|
|
|
# This name is all caps, lowercase it
|
|
elif name.isupper():
|
|
result = name.lower()
|
|
|
|
return supress_adjacent_capital_letters(result)
|
|
|
|
|
|
def camel_case(name):
|
|
result = name
|
|
|
|
splits = name.split("_")
|
|
splits = [captialize_first_letter(split) for split in splits]
|
|
result = "".join(splits)
|
|
|
|
return supress_adjacent_capital_letters(result)
|
|
|
|
|
|
def captialize_first_letter(name):
|
|
if name.isupper():
|
|
name = name.lower()
|
|
|
|
return name[0].upper() + name[1:]
|
|
|
|
|
|
# The generated code for "Apple Swift Protos" suppresses
|
|
# adjacent capital letters in lower_camel_case.
|
|
def supress_adjacent_capital_letters(name):
|
|
chars = []
|
|
lastWasUpper = False
|
|
for char in name:
|
|
if lastWasUpper:
|
|
char = char.lower()
|
|
chars.append(char)
|
|
lastWasUpper = char.isupper()
|
|
result = "".join(chars)
|
|
if result.endswith("Id"):
|
|
result = result[:-2] + "ID"
|
|
if result.endswith("Url"):
|
|
result = result[:-3] + "URL"
|
|
return result
|
|
|
|
|
|
def swift_type_for_proto_primitive_type(proto_type):
|
|
if proto_type == "string":
|
|
return "String"
|
|
elif proto_type == "uint64":
|
|
return "UInt64"
|
|
elif proto_type == "uint32":
|
|
return "UInt32"
|
|
elif proto_type == "fixed64":
|
|
return "UInt64"
|
|
elif proto_type == "int64":
|
|
return "Int64"
|
|
elif proto_type == "int32":
|
|
return "Int32"
|
|
elif proto_type == "bool":
|
|
return "Bool"
|
|
elif proto_type == "bytes":
|
|
return "Data"
|
|
elif proto_type == "double":
|
|
return "Double"
|
|
elif proto_type == "float":
|
|
return "Float"
|
|
else:
|
|
return None
|
|
|
|
|
|
def is_swift_primitive_type(proto_type):
|
|
return proto_type in (
|
|
"String",
|
|
"UInt64",
|
|
"UInt32",
|
|
"Int64",
|
|
"Int32",
|
|
"Bool",
|
|
"Data",
|
|
"Double",
|
|
"Float",
|
|
)
|
|
|
|
|
|
# Provides context for writing an indented block surrounded by braces.
|
|
#
|
|
# e.g.
|
|
#
|
|
# with BracedContext('class Foo', writer) as writer:
|
|
# with BracedContext('func bar() -> Bool', writer) as writer:
|
|
# return true
|
|
#
|
|
# Produces:
|
|
#
|
|
# class Foo {
|
|
# func bar() -> Bool {
|
|
# return true
|
|
# }
|
|
# }
|
|
#
|
|
class BracedContext:
|
|
def __init__(self, line, writer):
|
|
self.writer = writer
|
|
writer.add("%s {" % line)
|
|
|
|
def __enter__(self):
|
|
self.writer.push_indent()
|
|
return self.writer
|
|
|
|
def __exit__(self, *args):
|
|
self.writer.pop_indent()
|
|
self.writer.add("}")
|
|
|
|
|
|
class WriterContext:
|
|
def __init__(self, proto_name, swift_name, parent=None):
|
|
self.proto_name = proto_name
|
|
self.swift_name = swift_name
|
|
self.parent = parent
|
|
self.name_map = {}
|
|
|
|
|
|
class LineWriter:
|
|
def __init__(self, args):
|
|
self.contexts = []
|
|
# self.indent = 0
|
|
self.lines = []
|
|
self.args = args
|
|
self.current_indent = 0
|
|
|
|
def braced(self, line):
|
|
return BracedContext(line, self)
|
|
|
|
def push_indent(self):
|
|
self.current_indent = self.current_indent + 1
|
|
|
|
def pop_indent(self):
|
|
self.current_indent = self.current_indent - 1
|
|
if self.current_indent < 0:
|
|
raise Exception("Invalid indentation")
|
|
|
|
def all_context_proto_names(self):
|
|
return [context.proto_name for context in self.contexts]
|
|
|
|
def current_context(self):
|
|
return self.contexts[-1]
|
|
|
|
def indent(self):
|
|
return self.current_indent
|
|
# return len(self.contexts)
|
|
|
|
def push_context(self, proto_name, swift_name):
|
|
self.contexts.append(WriterContext(proto_name, swift_name))
|
|
self.push_indent()
|
|
|
|
def pop_context(self):
|
|
self.contexts.pop()
|
|
self.pop_indent()
|
|
|
|
def add(self, line):
|
|
self.lines.append((" " * self.indent()) + line)
|
|
|
|
def add_raw(self, line):
|
|
self.lines.append(line)
|
|
|
|
def extend(self, text):
|
|
for line in text.split("\n"):
|
|
self.add(line)
|
|
|
|
def join(self):
|
|
lines = [line.rstrip() for line in self.lines]
|
|
return "\n".join(lines)
|
|
|
|
def rstrip(self):
|
|
lines = self.lines
|
|
while len(lines) > 0 and len(lines[-1].strip()) == 0:
|
|
lines = lines[:-1]
|
|
self.lines = lines
|
|
|
|
def newline(self):
|
|
self.add("")
|
|
|
|
def needs_objc(self):
|
|
return proto_syntax == "proto2"
|
|
|
|
def add_objc(self):
|
|
if self.needs_objc():
|
|
self.add("@objc ")
|
|
|
|
|
|
class BaseContext(object):
|
|
def __init__(self):
|
|
self.parent = None
|
|
self.proto_name = None
|
|
|
|
def inherited_proto_names(self):
|
|
if self.parent is None:
|
|
return []
|
|
if self.proto_name is None:
|
|
return []
|
|
return self.parent.inherited_proto_names() + [
|
|
self.proto_name,
|
|
]
|
|
|
|
def derive_swift_name(self):
|
|
names = self.inherited_proto_names()
|
|
return self.args.wrapper_prefix + "".join(names)
|
|
|
|
def derive_wrapped_swift_name(self):
|
|
names = self.inherited_proto_names()
|
|
return self.args.proto_prefix + "_" + ".".join(names)
|
|
|
|
def qualified_proto_name(self):
|
|
names = self.inherited_proto_names()
|
|
return ".".join(names)
|
|
|
|
def children(self):
|
|
return []
|
|
|
|
def descendents(self):
|
|
result = []
|
|
for child in self.children():
|
|
result.append(child)
|
|
result.extend(child.descendents())
|
|
return result
|
|
|
|
def siblings(self):
|
|
result = []
|
|
if self.parent is not None:
|
|
result = self.parent.children()
|
|
return result
|
|
|
|
def ancestors(self):
|
|
result = []
|
|
if self.parent is not None:
|
|
result.append(self.parent)
|
|
result.extend(self.parent.ancestors())
|
|
return result
|
|
|
|
def context_for_proto_type(self, field):
|
|
should_deep_search = "." in field.proto_type
|
|
for candidate in self.all_known_contexts(should_deep_search=should_deep_search):
|
|
if candidate.proto_name == field.proto_type:
|
|
return candidate
|
|
if candidate.qualified_proto_name() == field.proto_type:
|
|
return candidate
|
|
if candidate.derive_swift_name() == field.proto_type:
|
|
return candidate
|
|
|
|
return None
|
|
|
|
def all_known_contexts(self, should_deep_search=False):
|
|
if should_deep_search:
|
|
root_ancestor = self.ancestors()[-1]
|
|
return root_ancestor.descendents()
|
|
|
|
candidates = []
|
|
candidates.extend(self.descendents())
|
|
candidates.extend(self.siblings())
|
|
for ancestor in self.ancestors():
|
|
if ancestor.proto_name is None:
|
|
# Ignore the root context
|
|
continue
|
|
candidates.append(ancestor)
|
|
candidates.extend(ancestor.siblings())
|
|
return candidates
|
|
|
|
def base_swift_type_for_field(self, field):
|
|
swift_type = swift_type_for_proto_primitive_type(field.proto_type)
|
|
if swift_type is not None:
|
|
return swift_type
|
|
else:
|
|
matching_context = self.context_for_proto_type(field)
|
|
if matching_context is not None:
|
|
return matching_context.swift_name
|
|
else:
|
|
# Failure
|
|
return field.proto_type
|
|
|
|
def swift_type_for_field(self, field, suppress_optional=False):
|
|
base_type = self.base_swift_type_for_field(field)
|
|
|
|
if field.rules == "optional":
|
|
if suppress_optional:
|
|
return base_type
|
|
can_be_optional = self.can_field_be_optional(field)
|
|
if can_be_optional:
|
|
return "%s?" % base_type
|
|
else:
|
|
return base_type
|
|
elif field.rules == "required":
|
|
return base_type
|
|
elif field.rules == "repeated":
|
|
return "[%s]" % base_type
|
|
else:
|
|
raise Exception("Unknown field type")
|
|
|
|
def is_field_primitive(self, field):
|
|
return field.proto_type in (
|
|
"uint64",
|
|
"uint32",
|
|
"fixed64",
|
|
"bool",
|
|
"double",
|
|
)
|
|
|
|
def can_field_be_optional(self, field):
|
|
return not self.is_field_primitive(field) or not field.is_required
|
|
|
|
def is_field_proto3_primitive(self, field):
|
|
return (
|
|
proto_syntax == "proto3"
|
|
and (self.is_field_primitive(field) or self.is_field_an_enum(field))
|
|
)
|
|
|
|
def is_field_an_enum(self, field):
|
|
matching_context = self.context_for_proto_type(field)
|
|
if matching_context is not None:
|
|
if type(matching_context) is EnumContext:
|
|
return True
|
|
return False
|
|
|
|
def is_field_oneof(self, field):
|
|
matching_context = self.context_for_proto_type(field)
|
|
if matching_context is not None:
|
|
if type(matching_context) is OneOfContext:
|
|
return True
|
|
return False
|
|
|
|
def is_field_a_proto(self, field):
|
|
matching_context = self.context_for_proto_type(field)
|
|
if matching_context is not None:
|
|
if type(matching_context) is MessageContext:
|
|
return True
|
|
return False
|
|
|
|
def is_field_a_proto_whose_init_throws(self, field):
|
|
matching_context = self.context_for_proto_type(field)
|
|
if matching_context is not None:
|
|
if type(matching_context) is MessageContext:
|
|
if matching_context.proto_name is self.proto_name:
|
|
for other_field in self.fields():
|
|
if other_field.index == field.index:
|
|
continue
|
|
elif self.context_for_proto_type(field).can_init_throw():
|
|
return True
|
|
return False
|
|
else:
|
|
return matching_context.can_init_throw()
|
|
return False
|
|
|
|
def can_field_be_optional_objc(self, field):
|
|
return (
|
|
self.can_field_be_optional(field)
|
|
and not self.is_field_primitive(field)
|
|
and not self.is_field_an_enum(field)
|
|
)
|
|
|
|
def default_value_for_field(self, field):
|
|
if field.rules == "repeated":
|
|
return "[]"
|
|
|
|
if field.default_value is not None and len(field.default_value) > 0:
|
|
return field.default_value
|
|
|
|
if field.rules == "optional":
|
|
can_be_optional = self.can_field_be_optional(field)
|
|
if can_be_optional:
|
|
return None # Swift provides this automatically.
|
|
|
|
if field.proto_type == "uint64":
|
|
return "0"
|
|
elif field.proto_type == "uint32":
|
|
return "0"
|
|
elif field.proto_type == "fixed64":
|
|
return "0"
|
|
elif field.proto_type == "double":
|
|
return "0"
|
|
elif field.proto_type == "bool":
|
|
return "false"
|
|
elif self.is_field_an_enum(field):
|
|
# TODO: Assert that rules is empty.
|
|
enum_context = self.context_for_proto_type(field)
|
|
return enum_context.default_value()
|
|
|
|
return None
|
|
|
|
|
|
class FileContext(BaseContext):
|
|
def __init__(self, args):
|
|
BaseContext.__init__(self)
|
|
|
|
self.args = args
|
|
|
|
self.messages = []
|
|
self.enums = []
|
|
|
|
def children(self):
|
|
return self.enums + self.messages
|
|
|
|
def prepare(self):
|
|
for child in self.children():
|
|
child.prepare()
|
|
|
|
def generate(self, writer):
|
|
writer.extend(
|
|
"""//
|
|
// Copyright 2022 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
public import SwiftProtobuf"""
|
|
)
|
|
|
|
writer.newline()
|
|
writer.extend(
|
|
"""
|
|
// WARNING: This code is generated. Only edit within the markers.
|
|
""".strip()
|
|
)
|
|
writer.newline()
|
|
|
|
writer.invalid_protobuf_error_name = "%sError" % self.args.wrapper_prefix
|
|
writer.extend(
|
|
(
|
|
"""
|
|
public enum %s: Error {
|
|
case invalidProtobuf(description: String)
|
|
}
|
|
"""
|
|
% writer.invalid_protobuf_error_name
|
|
).strip()
|
|
)
|
|
writer.newline()
|
|
|
|
for child in self.children():
|
|
child.generate(writer)
|
|
|
|
|
|
class MessageField:
|
|
def __init__(
|
|
self, name, index, rules, proto_type, default_value, sort_index, is_required
|
|
):
|
|
self.name = name
|
|
self.index = index
|
|
self.rules = rules
|
|
self.proto_type = proto_type
|
|
self.default_value = default_value
|
|
self.sort_index = sort_index
|
|
self.is_required = is_required
|
|
|
|
def has_accessor_name(self):
|
|
name = "has" + self.name_swift[0].upper() + self.name_swift[1:]
|
|
if name == "hasId":
|
|
# TODO: I'm not sure why "Apple Swift Proto" code formats the
|
|
# the name in this way.
|
|
name = "hasID"
|
|
elif name == "hasUrl":
|
|
# TODO: I'm not sure why "Apple Swift Proto" code formats the
|
|
# the name in this way.
|
|
name = "hasURL"
|
|
return name
|
|
|
|
|
|
class MessageContext(BaseContext):
|
|
def __init__(self, args, parent, proto_name):
|
|
BaseContext.__init__(self)
|
|
|
|
self.args = args
|
|
self.parent = parent
|
|
|
|
self.proto_name = proto_name
|
|
|
|
self.messages = []
|
|
self.enums = []
|
|
self.oneofs = []
|
|
|
|
self.field_map = {}
|
|
|
|
def fields(self):
|
|
fields = self.field_map.values()
|
|
fields = sorted(fields, key=lambda f: f.sort_index)
|
|
return fields
|
|
|
|
def field_indices(self):
|
|
return [field.index for field in self.fields()]
|
|
|
|
def field_names(self):
|
|
return [field.name for field in self.fields()]
|
|
|
|
def children(self):
|
|
return self.enums + self.messages + self.oneofs
|
|
|
|
def can_init_throw(self):
|
|
for field in self.fields():
|
|
if self.is_field_a_proto_whose_init_throws(field):
|
|
return True
|
|
if field.is_required and proto_syntax == "proto2":
|
|
return True
|
|
return False
|
|
|
|
def prepare(self):
|
|
self.swift_name = self.derive_swift_name()
|
|
self.swift_builder_name = "%sBuilder" % self.swift_name
|
|
|
|
for child in self.children():
|
|
child.prepare()
|
|
|
|
def generate(self, writer):
|
|
for child in self.messages:
|
|
child.generate(writer)
|
|
|
|
for child in self.enums:
|
|
child.generate(writer)
|
|
|
|
for child in self.oneofs:
|
|
child.generate(writer)
|
|
|
|
writer.add("// MARK: - %s" % self.swift_name)
|
|
writer.newline()
|
|
|
|
if writer.needs_objc():
|
|
writer.add_objc()
|
|
writer.add(
|
|
"public class %s: NSObject, Codable, NSSecureCoding {" % self.swift_name
|
|
)
|
|
else:
|
|
writer.add(
|
|
"public struct %s: Codable, CustomDebugStringConvertible {"
|
|
% self.swift_name
|
|
)
|
|
writer.newline()
|
|
|
|
writer.push_context(self.proto_name, self.swift_name)
|
|
|
|
wrapped_swift_name = self.derive_wrapped_swift_name()
|
|
|
|
# Prepare fields
|
|
explict_fields = []
|
|
implict_fields = []
|
|
for field in self.fields():
|
|
field.type_swift = self.swift_type_for_field(field)
|
|
field.type_swift_not_optional = self.swift_type_for_field(
|
|
field, suppress_optional=True
|
|
)
|
|
field.name_swift = lower_camel_case(field.name)
|
|
|
|
is_explicit = False
|
|
if field.is_required:
|
|
is_explicit = True
|
|
elif self.is_field_a_proto(field):
|
|
is_explicit = True
|
|
if is_explicit:
|
|
explict_fields.append(field)
|
|
else:
|
|
implict_fields.append(field)
|
|
|
|
# Ensure that no enum are required.
|
|
if (
|
|
proto_syntax == "proto2"
|
|
and self.is_field_an_enum(field)
|
|
and field.is_required
|
|
):
|
|
raise Exception(
|
|
"Enum fields cannot be required: %s.%s"
|
|
% (
|
|
self.proto_name,
|
|
field.name,
|
|
)
|
|
)
|
|
|
|
writer.add("fileprivate let proto: %s" % wrapped_swift_name)
|
|
writer.newline()
|
|
|
|
# Property Declarations
|
|
if len(explict_fields) > 0:
|
|
for field in explict_fields:
|
|
type_name = (
|
|
field.type_swift_not_optional
|
|
if field.is_required
|
|
else field.type_swift
|
|
)
|
|
writer.add_objc()
|
|
writer.add("public let %s: %s" % (field.name_swift, type_name))
|
|
|
|
if (
|
|
(not field.is_required)
|
|
and field.rules != "repeated"
|
|
and (not self.is_field_a_proto(field))
|
|
):
|
|
writer.add_objc()
|
|
writer.add("public var %s: Bool {" % field.has_accessor_name())
|
|
writer.push_indent()
|
|
writer.add("return proto.%s" % field.has_accessor_name())
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.newline()
|
|
|
|
for field in implict_fields:
|
|
if field.rules == "optional":
|
|
can_be_optional = not self.is_field_primitive(field) and not self.is_field_proto3_primitive(field)
|
|
if can_be_optional:
|
|
def write_field_getter(
|
|
is_objc_accessible, is_required_optional
|
|
):
|
|
if is_required_optional:
|
|
writer.add(
|
|
'// This "unwrapped" accessor should only be used if the "has value" accessor has already been checked.'
|
|
)
|
|
if is_objc_accessible:
|
|
writer.add_objc()
|
|
writer.add(
|
|
"public var unwrapped%s: %s {"
|
|
% (
|
|
camel_case(field.name_swift),
|
|
field.type_swift_not_optional,
|
|
)
|
|
)
|
|
writer.push_indent()
|
|
writer.add("if !%s {" % field.has_accessor_name())
|
|
writer.push_indent()
|
|
writer.add(
|
|
"// TODO: We could make this a crashing assert."
|
|
)
|
|
writer.add(
|
|
'owsFailDebug("Unsafe unwrap of missing optional: %s.%s.")'
|
|
% (
|
|
self.proto_name,
|
|
field.name_swift,
|
|
)
|
|
)
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
else:
|
|
if is_objc_accessible:
|
|
writer.add_objc()
|
|
writer.add(
|
|
"public var %s: %s? {"
|
|
% (
|
|
field.name_swift,
|
|
field.type_swift_not_optional,
|
|
)
|
|
)
|
|
writer.push_indent()
|
|
if not self.is_field_oneof(field):
|
|
writer.add(
|
|
"guard %s else {" % field.has_accessor_name()
|
|
)
|
|
writer.push_indent()
|
|
writer.add("return nil")
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
if self.is_field_an_enum(field):
|
|
enum_context = self.context_for_proto_type(field)
|
|
writer.add(
|
|
"return %s(proto.%s)"
|
|
% (
|
|
enum_context.wrap_func_name(),
|
|
field.name_swift,
|
|
)
|
|
)
|
|
elif self.is_field_oneof(field):
|
|
assert proto_syntax == "proto3"
|
|
oneof_context = self.context_for_proto_type(field)
|
|
writer.add(
|
|
"guard let %s = proto.%s else {"
|
|
% (
|
|
field.name_swift,
|
|
field.name_swift,
|
|
)
|
|
)
|
|
writer.push_indent()
|
|
writer.add("return nil")
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
if oneof_context.wrap_throws():
|
|
writer.add(
|
|
"guard let unwrapped%s = try? %s(%s) else {"
|
|
% (
|
|
camel_case(field.name_swift),
|
|
oneof_context.wrap_func_name(),
|
|
field.name_swift,
|
|
)
|
|
)
|
|
writer.push_indent()
|
|
writer.add(
|
|
'owsFailDebug("failed to unwrap %s")'
|
|
% field.name_swift
|
|
)
|
|
writer.add("return nil")
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.add(
|
|
"return unwrapped%s" % camel_case(field.name_swift)
|
|
)
|
|
else:
|
|
writer.add("return %s(%s)" % (oneof_context.wrap_func_name(), field.name_swift))
|
|
else:
|
|
writer.add("return proto.%s" % field.name_swift)
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
|
|
if self.is_field_an_enum(field):
|
|
write_field_getter(
|
|
is_objc_accessible=False, is_required_optional=False
|
|
)
|
|
write_field_getter(
|
|
is_objc_accessible=True, is_required_optional=True
|
|
)
|
|
elif self.is_field_oneof(field):
|
|
write_field_getter(
|
|
is_objc_accessible=False, is_required_optional=False
|
|
)
|
|
else:
|
|
write_field_getter(
|
|
is_objc_accessible=True, is_required_optional=False
|
|
)
|
|
else:
|
|
writer.add_objc()
|
|
writer.add(
|
|
"public var %s: %s {"
|
|
% (field.name_swift, field.type_swift_not_optional)
|
|
)
|
|
writer.push_indent()
|
|
if self.is_field_an_enum(field):
|
|
enum_context = self.context_for_proto_type(field)
|
|
writer.add(
|
|
"return %s(proto.%s)"
|
|
% (
|
|
enum_context.wrap_func_name(),
|
|
field.name_swift,
|
|
)
|
|
)
|
|
else:
|
|
writer.add("return proto.%s" % field.name_swift)
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
|
|
if not self.is_field_proto3_primitive(field) and not self.is_field_oneof(field):
|
|
writer.add_objc()
|
|
writer.add("public var %s: Bool {" % field.has_accessor_name())
|
|
writer.push_indent()
|
|
if proto_syntax == "proto3":
|
|
writer.add("return !proto.%s.isEmpty" % field.name_swift)
|
|
else:
|
|
writer.add("return proto.%s" % field.has_accessor_name())
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.newline()
|
|
elif field.rules == "repeated":
|
|
writer.add_objc()
|
|
writer.add(
|
|
"public var %s: %s {"
|
|
% (field.name_swift, field.type_swift_not_optional)
|
|
)
|
|
writer.push_indent()
|
|
writer.add("return proto.%s" % field.name_swift)
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.newline()
|
|
else:
|
|
writer.add_objc()
|
|
writer.add(
|
|
"public var %s: %s {"
|
|
% (field.name_swift, field.type_swift_not_optional)
|
|
)
|
|
writer.push_indent()
|
|
if self.is_field_an_enum(field):
|
|
enum_context = self.context_for_proto_type(field)
|
|
writer.add(
|
|
"return %s(proto.%s)"
|
|
% (
|
|
enum_context.unwrap_func_name(),
|
|
field.name_swift,
|
|
)
|
|
)
|
|
elif self.is_field_oneof(field):
|
|
oneof_context = self.context_for_proto_type(field)
|
|
writer.add(
|
|
"return %s(proto.%s)"
|
|
% (
|
|
oneof_context.unwrap_func_name(),
|
|
field.name_swift,
|
|
)
|
|
)
|
|
else:
|
|
writer.add("return proto.%s" % field.name_swift)
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.newline()
|
|
|
|
# Unknown fields
|
|
writer.add("public var hasUnknownFields: Bool {")
|
|
writer.push_indent()
|
|
writer.add("return !proto.unknownFields.data.isEmpty")
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
|
|
writer.add("public var unknownFields: SwiftProtobuf.UnknownStorage? {")
|
|
writer.push_indent()
|
|
writer.add("guard hasUnknownFields else { return nil }")
|
|
writer.add("return proto.unknownFields")
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.newline()
|
|
|
|
# Initializer
|
|
initializer_parameters = []
|
|
initializer_parameters.append("proto: %s" % wrapped_swift_name)
|
|
initializer_prefix = "private init("
|
|
for field in explict_fields:
|
|
type_name = (
|
|
field.type_swift_not_optional if field.is_required else field.type_swift
|
|
)
|
|
parameter = "%s: %s" % (field.name_swift, type_name)
|
|
parameter = "\n" + " " * len(initializer_prefix) + parameter
|
|
initializer_parameters.append(parameter)
|
|
initializer_parameters = ", ".join(initializer_parameters)
|
|
writer.extend(
|
|
"%s%s) {"
|
|
% (
|
|
initializer_prefix,
|
|
initializer_parameters,
|
|
)
|
|
)
|
|
writer.push_indent()
|
|
writer.add("self.proto = proto")
|
|
for field in explict_fields:
|
|
writer.add("self.%s = %s" % (field.name_swift, field.name_swift))
|
|
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.newline()
|
|
|
|
# serializedData() func
|
|
writer.add_objc()
|
|
writer.extend(
|
|
(
|
|
"""
|
|
public func serializedData() throws -> Data {
|
|
return try self.proto.serializedData()
|
|
}
|
|
"""
|
|
).strip()
|
|
)
|
|
writer.newline()
|
|
|
|
# init(serializedData:) func
|
|
if writer.needs_objc():
|
|
writer.add_objc()
|
|
writer.add("public convenience init(serializedData: Data) throws {")
|
|
else:
|
|
writer.add("public init(serializedData: Data) throws {")
|
|
writer.push_indent()
|
|
writer.add(
|
|
"let proto = try %s(serializedBytes: serializedData)" % (wrapped_swift_name,)
|
|
)
|
|
if self.can_init_throw():
|
|
writer.add("try self.init(proto)")
|
|
else:
|
|
writer.add("self.init(proto)")
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.newline()
|
|
|
|
# init(proto:) func
|
|
chunks = ["fileprivate"]
|
|
if writer.needs_objc():
|
|
chunks.append("convenience")
|
|
chunks.append(f"init(_ proto: {wrapped_swift_name})")
|
|
if self.can_init_throw():
|
|
chunks.append("throws")
|
|
chunks.append("{")
|
|
writer.add(" ".join(chunks))
|
|
writer.push_indent()
|
|
|
|
for field in explict_fields:
|
|
if field.is_required:
|
|
|
|
if proto_syntax == "proto2":
|
|
writer.add("guard proto.%s else {" % field.has_accessor_name())
|
|
writer.push_indent()
|
|
writer.add(
|
|
'throw %s.invalidProtobuf(description: "\\(Self.logTag()) missing required field: %s")'
|
|
% (
|
|
writer.invalid_protobuf_error_name,
|
|
field.name_swift,
|
|
)
|
|
)
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
|
|
if self.is_field_an_enum(field):
|
|
# TODO: Assert that rules is empty.
|
|
enum_context = self.context_for_proto_type(field)
|
|
writer.add(
|
|
"let %s = %s(proto.%s)"
|
|
% (
|
|
field.name_swift,
|
|
enum_context.wrap_func_name(),
|
|
field.name_swift,
|
|
)
|
|
)
|
|
elif self.is_field_a_proto_whose_init_throws(field):
|
|
writer.add(
|
|
"let %s = try %s(proto.%s)"
|
|
% (
|
|
field.name_swift,
|
|
self.base_swift_type_for_field(field),
|
|
field.name_swift,
|
|
)
|
|
),
|
|
elif self.is_field_a_proto(field):
|
|
writer.add(
|
|
"let %s = %s(proto.%s)"
|
|
% (
|
|
field.name_swift,
|
|
self.base_swift_type_for_field(field),
|
|
field.name_swift,
|
|
)
|
|
),
|
|
else:
|
|
writer.add(
|
|
"let %s = proto.%s"
|
|
% (
|
|
field.name_swift,
|
|
field.name_swift,
|
|
)
|
|
)
|
|
writer.newline()
|
|
continue
|
|
|
|
default_value = self.default_value_for_field(field)
|
|
if default_value is None:
|
|
writer.add("var %s: %s" % (field.name_swift, field.type_swift))
|
|
else:
|
|
writer.add(
|
|
"var %s: %s = %s"
|
|
% (field.name_swift, field.type_swift, default_value)
|
|
)
|
|
|
|
if field.rules == "repeated":
|
|
if self.is_field_an_enum(field):
|
|
enum_context = self.context_for_proto_type(field)
|
|
writer.add(
|
|
"%s = proto.%s.map { %s($0) }"
|
|
% (
|
|
field.name_swift,
|
|
field.name_swift,
|
|
enum_context.wrap_func_name(),
|
|
)
|
|
)
|
|
elif self.is_field_a_proto_whose_init_throws(field):
|
|
writer.add(
|
|
"%s = try proto.%s.map { try %s($0) }"
|
|
% (
|
|
field.name_swift,
|
|
field.name_swift,
|
|
self.base_swift_type_for_field(field),
|
|
)
|
|
)
|
|
elif self.is_field_a_proto(field):
|
|
writer.add(
|
|
"%s = proto.%s.map { %s($0) }"
|
|
% (
|
|
field.name_swift,
|
|
field.name_swift,
|
|
self.base_swift_type_for_field(field),
|
|
)
|
|
)
|
|
else:
|
|
writer.add(
|
|
"%s = proto.%s"
|
|
% (
|
|
field.name_swift,
|
|
field.name_swift,
|
|
)
|
|
)
|
|
else:
|
|
writer.add("if proto.%s {" % field.has_accessor_name())
|
|
writer.push_indent()
|
|
|
|
if self.is_field_an_enum(field):
|
|
# TODO: Assert that rules is empty.
|
|
enum_context = self.context_for_proto_type(field)
|
|
writer.add(
|
|
"%s = %s(proto.%s)"
|
|
% (
|
|
field.name_swift,
|
|
enum_context.wrap_func_name(),
|
|
field.name_swift,
|
|
)
|
|
)
|
|
elif self.is_field_a_proto_whose_init_throws(field):
|
|
writer.add(
|
|
"%s = try %s(proto.%s)"
|
|
% (
|
|
field.name_swift,
|
|
self.base_swift_type_for_field(field),
|
|
field.name_swift,
|
|
)
|
|
),
|
|
elif self.is_field_a_proto(field):
|
|
writer.add(
|
|
"%s = %s(proto.%s)"
|
|
% (
|
|
field.name_swift,
|
|
self.base_swift_type_for_field(field),
|
|
field.name_swift,
|
|
)
|
|
),
|
|
else:
|
|
writer.add(
|
|
"%s = proto.%s"
|
|
% (
|
|
field.name_swift,
|
|
field.name_swift,
|
|
)
|
|
)
|
|
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.newline()
|
|
|
|
initializer_prefix = "self.init("
|
|
initializer_arguments = []
|
|
initializer_arguments.append("proto: proto")
|
|
for field in explict_fields:
|
|
argument = "%s: %s" % (field.name_swift, field.name_swift)
|
|
argument = "\n" + " " * len(initializer_prefix) + argument
|
|
initializer_arguments.append(argument)
|
|
initializer_arguments = ", ".join(initializer_arguments)
|
|
writer.extend(
|
|
"%s%s)"
|
|
% (
|
|
initializer_prefix,
|
|
initializer_arguments,
|
|
)
|
|
)
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.newline()
|
|
|
|
# codable
|
|
|
|
if writer.needs_objc():
|
|
writer.add(
|
|
"public required convenience init(from decoder: Swift.Decoder) throws {"
|
|
)
|
|
else:
|
|
writer.add("public init(from decoder: Swift.Decoder) throws {")
|
|
writer.push_indent()
|
|
writer.add("let singleValueContainer = try decoder.singleValueContainer()")
|
|
writer.add("let serializedData = try singleValueContainer.decode(Data.self)")
|
|
writer.add("try self.init(serializedData: serializedData)")
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
|
|
writer.add("public func encode(to encoder: Swift.Encoder) throws {")
|
|
writer.push_indent()
|
|
writer.add("var singleValueContainer = encoder.singleValueContainer()")
|
|
writer.add("try singleValueContainer.encode(try serializedData())")
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.newline()
|
|
|
|
# NSSecureCoding
|
|
if writer.needs_objc():
|
|
writer.add("public static var supportsSecureCoding: Bool { true }")
|
|
writer.newline()
|
|
|
|
writer.add("public required convenience init?(coder: NSCoder) {")
|
|
writer.push_indent()
|
|
writer.add(
|
|
"guard let serializedData = coder.decodeData() else { return nil }"
|
|
)
|
|
writer.add("do {")
|
|
writer.push_indent()
|
|
writer.add("try self.init(serializedData: serializedData)")
|
|
writer.pop_indent()
|
|
writer.add("} catch {")
|
|
writer.push_indent()
|
|
writer.add('owsFailDebug("Failed to decode serialized data \\(error)")')
|
|
writer.add("return nil")
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.newline()
|
|
|
|
writer.add("public func encode(with coder: NSCoder) {")
|
|
writer.push_indent()
|
|
writer.add("do {")
|
|
writer.push_indent()
|
|
writer.add("coder.encode(try serializedData())")
|
|
writer.pop_indent()
|
|
writer.add("} catch {")
|
|
writer.push_indent()
|
|
writer.add('owsFailDebug("Failed to encode serialized data \\(error)")')
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.newline()
|
|
|
|
# description
|
|
if writer.needs_objc():
|
|
writer.add_objc()
|
|
writer.add("public override var debugDescription: String {")
|
|
else:
|
|
writer.add("public var debugDescription: String {")
|
|
writer.push_indent()
|
|
writer.add('return "\\(proto)"')
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.newline()
|
|
|
|
writer.pop_context()
|
|
|
|
writer.rstrip()
|
|
writer.add("}")
|
|
writer.newline()
|
|
self.generate_builder(writer)
|
|
self.generate_debug_extension(writer)
|
|
|
|
def generate_debug_extension(self, writer):
|
|
writer.add("#if TESTABLE_BUILD")
|
|
writer.newline()
|
|
with writer.braced("extension %s" % self.swift_name) as writer:
|
|
writer.add_objc()
|
|
with writer.braced(
|
|
"public func serializedDataIgnoringErrors() -> Data?"
|
|
) as writer:
|
|
writer.add("return try! self.serializedData()")
|
|
|
|
writer.newline()
|
|
|
|
with writer.braced("extension %s" % self.swift_builder_name) as writer:
|
|
writer.add_objc()
|
|
with writer.braced(
|
|
"public func buildIgnoringErrors() -> %s?" % self.swift_name
|
|
) as writer:
|
|
if self.can_init_throw():
|
|
writer.add("return try! self.build()")
|
|
else:
|
|
writer.add("return self.buildInfallibly()")
|
|
|
|
writer.newline()
|
|
writer.add("#endif")
|
|
writer.newline()
|
|
|
|
def generate_builder(self, writer):
|
|
|
|
wrapped_swift_name = self.derive_wrapped_swift_name()
|
|
|
|
# Required Fields
|
|
required_fields = [field for field in self.fields() if field.is_required]
|
|
required_init_params = []
|
|
required_init_args = []
|
|
if len(required_fields) > 0:
|
|
for field in required_fields:
|
|
if field.rules == "repeated":
|
|
param_type = "[" + self.base_swift_type_for_field(field) + "]"
|
|
else:
|
|
param_type = self.base_swift_type_for_field(field)
|
|
required_init_params.append("%s: %s" % (field.name_swift, param_type))
|
|
required_init_args.append(
|
|
"%s: %s" % (field.name_swift, field.name_swift)
|
|
)
|
|
|
|
with writer.braced("extension %s" % self.swift_name) as writer:
|
|
# Convenience accessor.
|
|
writer.add_objc()
|
|
with writer.braced(
|
|
"public static func builder(%s) -> %s"
|
|
% (
|
|
", ".join(required_init_params),
|
|
self.swift_builder_name,
|
|
)
|
|
) as writer:
|
|
writer.add(
|
|
"return %s(%s)"
|
|
% (
|
|
self.swift_builder_name,
|
|
", ".join(required_init_args),
|
|
)
|
|
)
|
|
writer.newline()
|
|
|
|
# asBuilder()
|
|
writer.add(
|
|
"// asBuilder() constructs a builder that reflects the proto's contents."
|
|
)
|
|
writer.add_objc()
|
|
with writer.braced(
|
|
"public func asBuilder() -> %s" % (self.swift_builder_name,)
|
|
) as writer:
|
|
if writer.needs_objc():
|
|
writer.add(
|
|
"let builder = %s(%s)"
|
|
% (
|
|
self.swift_builder_name,
|
|
", ".join(required_init_args),
|
|
)
|
|
)
|
|
else:
|
|
writer.add(
|
|
"var builder = %s(%s)"
|
|
% (
|
|
self.swift_builder_name,
|
|
", ".join(required_init_args),
|
|
)
|
|
)
|
|
|
|
for field in self.fields():
|
|
if field.is_required:
|
|
continue
|
|
|
|
accessor_name = field.name_swift
|
|
accessor_name = "set" + accessor_name[0].upper() + accessor_name[1:]
|
|
|
|
can_be_optional = not self.is_field_primitive(field)
|
|
if field.rules == "repeated" or self.is_field_proto3_primitive(field):
|
|
writer.add(
|
|
"builder.%s(%s)"
|
|
% (
|
|
accessor_name,
|
|
field.name_swift,
|
|
)
|
|
)
|
|
elif can_be_optional:
|
|
writer.add("if let _value = %s {" % field.name_swift)
|
|
writer.push_indent()
|
|
writer.add("builder.%s(_value)" % (accessor_name,))
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
else:
|
|
writer.add("if %s {" % field.has_accessor_name())
|
|
writer.push_indent()
|
|
writer.add(
|
|
"builder.%s(%s)"
|
|
% (
|
|
accessor_name,
|
|
field.name_swift,
|
|
)
|
|
)
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
|
|
writer.add("if let _value = unknownFields {")
|
|
writer.push_indent()
|
|
writer.add("builder.setUnknownFields(_value)")
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
|
|
writer.add("return builder")
|
|
writer.newline()
|
|
|
|
if writer.needs_objc():
|
|
writer.add_objc()
|
|
writer.add("public class %s: NSObject {" % self.swift_builder_name)
|
|
else:
|
|
writer.add("public struct %s {" % self.swift_builder_name)
|
|
writer.newline()
|
|
|
|
writer.push_context(self.proto_name, self.swift_name)
|
|
|
|
writer.add("private var proto = %s()" % wrapped_swift_name)
|
|
writer.newline()
|
|
|
|
# Initializer
|
|
if writer.needs_objc():
|
|
writer.add_objc()
|
|
writer.add("fileprivate override init() {}")
|
|
else:
|
|
writer.add("fileprivate init() {}")
|
|
writer.newline()
|
|
|
|
# Required-Field Initializer
|
|
if len(required_fields) > 0:
|
|
# writer.add('// Initializer for required fields')
|
|
writer.add_objc()
|
|
writer.add("fileprivate init(%s) {" % ", ".join(required_init_params))
|
|
writer.push_indent()
|
|
if writer.needs_objc():
|
|
writer.add("super.init()")
|
|
writer.newline()
|
|
for field in required_fields:
|
|
accessor_name = field.name_swift
|
|
accessor_name = "set" + accessor_name[0].upper() + accessor_name[1:]
|
|
writer.add(
|
|
"%s(%s)"
|
|
% (
|
|
accessor_name,
|
|
field.name_swift,
|
|
)
|
|
)
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.newline()
|
|
|
|
# Setters
|
|
for field in self.fields():
|
|
if field.rules == "repeated":
|
|
# Add
|
|
accessor_name = field.name_swift
|
|
accessor_name = "add" + accessor_name[0].upper() + accessor_name[1:]
|
|
if writer.needs_objc():
|
|
writer.add_objc()
|
|
writer.add(
|
|
"public func %s(_ valueParam: %s) {"
|
|
% (
|
|
accessor_name,
|
|
self.base_swift_type_for_field(field),
|
|
)
|
|
)
|
|
else:
|
|
writer.add(
|
|
"public mutating func %s(_ valueParam: %s) {"
|
|
% (
|
|
accessor_name,
|
|
self.base_swift_type_for_field(field),
|
|
)
|
|
)
|
|
writer.push_indent()
|
|
if self.is_field_an_enum(field):
|
|
enum_context = self.context_for_proto_type(field)
|
|
param = "%s(valueParam)" % enum_context.unwrap_func_name()
|
|
elif self.is_field_oneof(field):
|
|
oneof_context = self.context_for_proto_type(field)
|
|
param = "%s(valueParam)" % oneof_context.unwrap_func_name()
|
|
elif self.is_field_a_proto(field):
|
|
param = "valueParam.proto"
|
|
else:
|
|
param = "valueParam"
|
|
writer.add("proto.%s.append(%s)" % (field.name_swift, param))
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.newline()
|
|
|
|
# Set
|
|
accessor_name = field.name_swift
|
|
accessor_name = "set" + accessor_name[0].upper() + accessor_name[1:]
|
|
if writer.needs_objc():
|
|
writer.add_objc()
|
|
writer.add(
|
|
"public func %s(_ wrappedItems: [%s]) {"
|
|
% (
|
|
accessor_name,
|
|
self.base_swift_type_for_field(field),
|
|
)
|
|
)
|
|
else:
|
|
writer.add(
|
|
"public mutating func %s(_ wrappedItems: [%s]) {"
|
|
% (
|
|
accessor_name,
|
|
self.base_swift_type_for_field(field),
|
|
)
|
|
)
|
|
writer.push_indent()
|
|
if self.is_field_an_enum(field):
|
|
enum_context = self.context_for_proto_type(field)
|
|
writer.add(
|
|
"proto.%s = wrappedItems.map { %s($0) }"
|
|
% (
|
|
field.name_swift,
|
|
enum_context.unwrap_func_name(),
|
|
)
|
|
)
|
|
elif self.is_field_a_proto(field):
|
|
writer.add(
|
|
"proto.%s = wrappedItems.map { $0.proto }" % (field.name_swift,)
|
|
)
|
|
else:
|
|
writer.add("proto.%s = wrappedItems" % (field.name_swift,))
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.newline()
|
|
else:
|
|
accessor_name = field.name_swift
|
|
accessor_name = "set" + accessor_name[0].upper() + accessor_name[1:]
|
|
|
|
# for fields that are supported as optionals in objc, we will add an objc only setter that takes an optional value
|
|
can_field_be_optional_objc = self.can_field_be_optional_objc(field)
|
|
if can_field_be_optional_objc:
|
|
writer.add_objc()
|
|
writer.add(
|
|
"@available(swift, obsoleted: 1.0)"
|
|
) # Don't allow using this function in Swift
|
|
if writer.needs_objc():
|
|
writer.add(
|
|
"public func %s(_ valueParam: %s) {"
|
|
% (accessor_name, self.swift_type_for_field(field))
|
|
)
|
|
else:
|
|
writer.add(
|
|
"public mutating func %s(_ valueParam: %s) {"
|
|
% (accessor_name, self.swift_type_for_field(field))
|
|
)
|
|
writer.push_indent()
|
|
writer.add("guard let valueParam = valueParam else { return }")
|
|
|
|
if self.is_field_an_enum(field):
|
|
enum_context = self.context_for_proto_type(field)
|
|
writer.add(
|
|
"proto.%s = %s(valueParam)"
|
|
% (
|
|
field.name_swift,
|
|
enum_context.unwrap_func_name(),
|
|
)
|
|
)
|
|
elif self.is_field_oneof(field):
|
|
oneof_context = self.context_for_proto_type(field)
|
|
writer.add(
|
|
"proto.%s = %s(valueParam)"
|
|
% (
|
|
field.name_swift,
|
|
oneof_context.unwrap_func_name(),
|
|
)
|
|
)
|
|
elif self.is_field_a_proto(field):
|
|
writer.add("proto.%s = valueParam.proto" % (field.name_swift,))
|
|
else:
|
|
writer.add("proto.%s = valueParam" % (field.name_swift,))
|
|
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.newline()
|
|
|
|
# Only allow the nonnull setter in objc if the field can't be optional
|
|
if not can_field_be_optional_objc:
|
|
writer.add_objc()
|
|
if writer.needs_objc():
|
|
writer.add(
|
|
"public func %s(_ valueParam: %s) {"
|
|
% (accessor_name, self.base_swift_type_for_field(field))
|
|
)
|
|
else:
|
|
writer.add(
|
|
"public mutating func %s(_ valueParam: %s) {"
|
|
% (accessor_name, self.base_swift_type_for_field(field))
|
|
)
|
|
writer.push_indent()
|
|
|
|
if self.is_field_an_enum(field):
|
|
enum_context = self.context_for_proto_type(field)
|
|
writer.add(
|
|
"proto.%s = %s(valueParam)"
|
|
% (
|
|
field.name_swift,
|
|
enum_context.unwrap_func_name(),
|
|
)
|
|
)
|
|
elif self.is_field_oneof(field):
|
|
oneof_context = self.context_for_proto_type(field)
|
|
writer.add(
|
|
"proto.%s = %s(valueParam)"
|
|
% (
|
|
field.name_swift,
|
|
oneof_context.unwrap_func_name(),
|
|
)
|
|
)
|
|
elif self.is_field_a_proto(field):
|
|
writer.add("proto.%s = valueParam.proto" % (field.name_swift,))
|
|
else:
|
|
writer.add("proto.%s = valueParam" % (field.name_swift,))
|
|
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.newline()
|
|
|
|
# Unknown fields setter
|
|
if writer.needs_objc():
|
|
writer.add(
|
|
"public func setUnknownFields(_ unknownFields: SwiftProtobuf.UnknownStorage) {"
|
|
)
|
|
else:
|
|
writer.add(
|
|
"public mutating func setUnknownFields(_ unknownFields: SwiftProtobuf.UnknownStorage) {"
|
|
)
|
|
writer.push_indent()
|
|
writer.add("proto.unknownFields = unknownFields")
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.newline()
|
|
|
|
# build() func
|
|
if self.can_init_throw():
|
|
writer.add_objc()
|
|
writer.add("public func build() throws -> %s {" % self.swift_name)
|
|
writer.push_indent()
|
|
writer.add("return try %s(proto)" % self.swift_name)
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.newline()
|
|
else:
|
|
writer.add_objc()
|
|
writer.add("public func buildInfallibly() -> %s {" % self.swift_name)
|
|
writer.push_indent()
|
|
writer.add("return %s(proto)" % self.swift_name)
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.newline()
|
|
|
|
# buildSerializedData() func
|
|
writer.add_objc()
|
|
writer.add("public func buildSerializedData() throws -> Data {")
|
|
writer.push_indent()
|
|
writer.add("return try %s(proto).serializedData()" % self.swift_name)
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.newline()
|
|
|
|
writer.pop_context()
|
|
|
|
writer.rstrip()
|
|
writer.add("}")
|
|
writer.newline()
|
|
|
|
|
|
class EnumContext(BaseContext):
|
|
def __init__(self, args, parent, proto_name):
|
|
BaseContext.__init__(self)
|
|
|
|
self.args = args
|
|
self.parent = parent
|
|
self.proto_name = proto_name
|
|
|
|
# self.item_names = set()
|
|
# self.item_indices = set()
|
|
self.item_map = {}
|
|
|
|
def derive_wrapped_swift_name(self):
|
|
# return BaseContext.derive_wrapped_swift_name(self) + 'Enum'
|
|
result = BaseContext.derive_wrapped_swift_name(self)
|
|
if self.proto_name == "Type":
|
|
result = result + "Enum"
|
|
return result
|
|
|
|
def fully_qualify_wrappers(self):
|
|
return False
|
|
|
|
def wrap_func_name(self):
|
|
if self.fully_qualify_wrappers():
|
|
return "%s.%sWrap" % (
|
|
self.parent.swift_name,
|
|
self.swift_name,
|
|
)
|
|
return "%sWrap" % (self.swift_name,)
|
|
|
|
def unwrap_func_name(self):
|
|
if self.fully_qualify_wrappers():
|
|
return "%s.%sUnwrap" % (
|
|
self.parent.swift_name,
|
|
self.swift_name,
|
|
)
|
|
return "%sUnwrap" % (self.swift_name,)
|
|
|
|
def item_names(self):
|
|
return self.item_map.values()
|
|
|
|
def item_indices(self):
|
|
return self.item_map.keys()
|
|
|
|
def prepare(self):
|
|
self.swift_name = self.derive_swift_name()
|
|
|
|
for child in self.children():
|
|
child.prepare()
|
|
|
|
def case_pairs(self):
|
|
indices = [int(index) for index in self.item_indices()]
|
|
indices = sorted(indices)
|
|
result = []
|
|
for index in indices:
|
|
index_str = str(index)
|
|
item_name = self.item_map[index_str]
|
|
case_name = lower_camel_case(item_name)
|
|
result.append(
|
|
(
|
|
case_name,
|
|
index_str,
|
|
)
|
|
)
|
|
return result
|
|
|
|
def default_value(self):
|
|
for case_name, case_index in self.case_pairs():
|
|
return "." + case_name
|
|
|
|
def generate(self, writer):
|
|
|
|
writer.add("// MARK: - %s" % self.swift_name)
|
|
writer.newline()
|
|
|
|
if proto_syntax == "proto3":
|
|
# proto3 enums are completely different.
|
|
# Swift-only, with Int rawValue.
|
|
writer.add("public enum %s: SwiftProtobuf.Enum {" % self.swift_name)
|
|
|
|
writer.push_context(self.proto_name, self.swift_name)
|
|
|
|
writer.add("public typealias RawValue = Int")
|
|
|
|
max_case_index = 0
|
|
for case_name, case_index in self.case_pairs():
|
|
if case_name in ["default", "true", "false"]:
|
|
case_name = "`%s`" % case_name
|
|
writer.add(
|
|
"case %s // %s"
|
|
% (
|
|
case_name,
|
|
case_index,
|
|
)
|
|
)
|
|
max_case_index = max(max_case_index, int(case_index))
|
|
|
|
writer.add("case UNRECOGNIZED(Int)")
|
|
|
|
writer.newline()
|
|
writer.add("public init() {")
|
|
writer.push_indent()
|
|
for case_name, case_index in self.case_pairs():
|
|
writer.add("self = .%s" % case_name)
|
|
break
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
|
|
writer.newline()
|
|
writer.add("public init?(rawValue: Int) {")
|
|
writer.push_indent()
|
|
writer.add("switch rawValue {")
|
|
writer.push_indent()
|
|
for case_name, case_index in self.case_pairs():
|
|
writer.add("case %s: self = .%s" % (case_index, case_name))
|
|
writer.add("default: self = .UNRECOGNIZED(rawValue)")
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
|
|
writer.newline()
|
|
writer.add("public var rawValue: Int {")
|
|
writer.push_indent()
|
|
writer.add("switch self {")
|
|
writer.push_indent()
|
|
for case_name, case_index in self.case_pairs():
|
|
writer.add(
|
|
"case .%s: return %s"
|
|
% (
|
|
case_name,
|
|
case_index,
|
|
)
|
|
)
|
|
writer.add("case .UNRECOGNIZED(let i): return i")
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
|
|
writer.pop_context()
|
|
|
|
writer.rstrip()
|
|
writer.add("}")
|
|
writer.newline()
|
|
else:
|
|
writer.add_objc()
|
|
writer.add("public enum %s: Int32 {" % self.swift_name)
|
|
|
|
writer.push_context(self.proto_name, self.swift_name)
|
|
|
|
max_case_index = 0
|
|
for case_name, case_index in self.case_pairs():
|
|
if case_name in ["default", "true", "false"]:
|
|
case_name = "`%s`" % case_name
|
|
writer.add(
|
|
"case %s = %s"
|
|
% (
|
|
case_name,
|
|
case_index,
|
|
)
|
|
)
|
|
max_case_index = max(max_case_index, int(case_index))
|
|
|
|
writer.pop_context()
|
|
|
|
writer.rstrip()
|
|
writer.add("}")
|
|
writer.newline()
|
|
|
|
wrapped_swift_name = self.derive_wrapped_swift_name()
|
|
writer.add(
|
|
"private func %sWrap(_ value: %s) -> %s {"
|
|
% (
|
|
self.swift_name,
|
|
wrapped_swift_name,
|
|
self.swift_name,
|
|
)
|
|
)
|
|
writer.push_indent()
|
|
writer.add("switch value {")
|
|
for case_name, case_index in self.case_pairs():
|
|
writer.add(
|
|
"case .%s: return .%s"
|
|
% (
|
|
case_name,
|
|
case_name,
|
|
)
|
|
)
|
|
if proto_syntax == "proto3":
|
|
writer.add("case .UNRECOGNIZED(let i): return .UNRECOGNIZED(i)")
|
|
|
|
writer.add("}")
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.newline()
|
|
writer.add(
|
|
"private func %sUnwrap(_ value: %s) -> %s {"
|
|
% (
|
|
self.swift_name,
|
|
self.swift_name,
|
|
wrapped_swift_name,
|
|
)
|
|
)
|
|
writer.push_indent()
|
|
writer.add("switch value {")
|
|
for case_name, case_index in self.case_pairs():
|
|
writer.add(
|
|
"case .%s: return .%s"
|
|
% (
|
|
case_name,
|
|
case_name,
|
|
)
|
|
)
|
|
if proto_syntax == "proto3":
|
|
writer.add("case .UNRECOGNIZED(let i): return .UNRECOGNIZED(i)")
|
|
writer.add("}")
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.newline()
|
|
|
|
|
|
class OneOfContext(BaseContext):
|
|
def __init__(self, args, parent, proto_name):
|
|
BaseContext.__init__(self)
|
|
|
|
self.args = args
|
|
self.parent = parent
|
|
self.proto_name = camel_case(proto_name)
|
|
|
|
self.item_type_map = {}
|
|
self.item_index_map = {}
|
|
|
|
def derive_swift_name(self):
|
|
names = self.inherited_proto_names()
|
|
names.insert(-1, "OneOf")
|
|
return self.args.wrapper_prefix + "".join(names)
|
|
|
|
def derive_wrapped_swift_name(self):
|
|
names = self.inherited_proto_names()
|
|
names[-1] = "OneOf_" + self.proto_name
|
|
return self.args.proto_prefix + "_" + ".".join(names)
|
|
|
|
def qualified_proto_name(self):
|
|
names = self.inherited_proto_names()
|
|
names[-1] = "OneOf_" + self.proto_name
|
|
return ".".join(names)
|
|
|
|
def item_names(self):
|
|
return self.item_index_map.values()
|
|
|
|
def item_indices(self):
|
|
return self.item_index_map.keys()
|
|
|
|
def sorted_item_indices(self):
|
|
indices = [int(index) for index in self.item_indices()]
|
|
return sorted(indices)
|
|
|
|
def last_index(self):
|
|
return self.sorted_item_indices()[-1]
|
|
|
|
def wrap_func_name(self):
|
|
return "%sWrap" % (self.swift_name,)
|
|
|
|
def unwrap_func_name(self):
|
|
return "%sUnwrap" % (self.swift_name,)
|
|
|
|
def prepare(self):
|
|
self.swift_name = self.derive_swift_name()
|
|
|
|
def context_for_proto_type(self, proto_type):
|
|
for candidate in self.all_known_contexts(should_deep_search=False):
|
|
if candidate.proto_name == proto_type:
|
|
return candidate
|
|
if candidate.qualified_proto_name() == proto_type:
|
|
return candidate
|
|
|
|
return None
|
|
|
|
def case_tuples(self):
|
|
result = []
|
|
for index in self.sorted_item_indices():
|
|
index_str = str(index)
|
|
item_name = self.item_index_map[index_str]
|
|
item_type = self.item_type_map[item_name]
|
|
case_name = lower_camel_case(item_name)
|
|
case_type = swift_type_for_proto_primitive_type(item_type)
|
|
case_throws = False
|
|
if case_type is None:
|
|
case_type = self.context_for_proto_type(item_type).swift_name
|
|
case_throws = self.is_field_a_proto_whose_init_throws(item_type)
|
|
result.append((case_name, case_type, case_throws))
|
|
return result
|
|
|
|
def wrap_throws(self):
|
|
return any(case_throws for _, _, case_throws in self.case_tuples())
|
|
|
|
def generate(self, writer):
|
|
|
|
writer.add("// MARK: - %s" % self.swift_name)
|
|
writer.newline()
|
|
|
|
# proto3 enums are completely different.
|
|
# Swift-only, with Int rawValue.
|
|
writer.add("public enum %s {" % self.swift_name)
|
|
|
|
writer.push_context(self.proto_name, self.swift_name)
|
|
|
|
for case_name, case_type, _ in self.case_tuples():
|
|
writer.add(
|
|
"case %s(%s)"
|
|
% (
|
|
case_name,
|
|
case_type,
|
|
)
|
|
)
|
|
|
|
writer.pop_context()
|
|
|
|
writer.rstrip()
|
|
writer.add("}")
|
|
writer.newline()
|
|
|
|
wrapped_swift_name = self.derive_wrapped_swift_name()
|
|
writer.add(
|
|
"private func %sWrap(_ value: %s)%s -> %s {"
|
|
% (
|
|
self.swift_name,
|
|
wrapped_swift_name,
|
|
" throws" if self.wrap_throws() else "",
|
|
self.swift_name,
|
|
)
|
|
)
|
|
writer.push_indent()
|
|
writer.add("switch value {")
|
|
for case_name, case_type, case_throws in self.case_tuples():
|
|
if is_swift_primitive_type(case_type):
|
|
writer.add(
|
|
"case .%s(let value): return .%s(value)"
|
|
% (
|
|
case_name,
|
|
case_name,
|
|
)
|
|
)
|
|
elif case_throws:
|
|
writer.add(
|
|
"case .%s(let value): return .%s(try %s(value))"
|
|
% (
|
|
case_name,
|
|
case_name,
|
|
case_type,
|
|
)
|
|
)
|
|
else:
|
|
writer.add(
|
|
"case .%s(let value): return .%s(%s(value))"
|
|
% (
|
|
case_name,
|
|
case_name,
|
|
case_type,
|
|
)
|
|
)
|
|
|
|
writer.add("}")
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.newline()
|
|
|
|
writer.add(
|
|
"private func %sUnwrap(_ value: %s) -> %s {"
|
|
% (
|
|
self.swift_name,
|
|
self.swift_name,
|
|
wrapped_swift_name,
|
|
)
|
|
)
|
|
writer.push_indent()
|
|
writer.add("switch value {")
|
|
for case_name, case_type, case_throws in self.case_tuples():
|
|
if is_swift_primitive_type(case_type):
|
|
writer.add(
|
|
"case .%s(let value): return .%s(value)"
|
|
% (
|
|
case_name,
|
|
case_name,
|
|
)
|
|
)
|
|
else:
|
|
writer.add(
|
|
"case .%s(let value): return .%s(value.proto)"
|
|
% (
|
|
case_name,
|
|
case_name,
|
|
)
|
|
)
|
|
writer.add("}")
|
|
writer.pop_indent()
|
|
writer.add("}")
|
|
writer.newline()
|
|
|
|
|
|
class LineParser:
|
|
def __init__(self, text):
|
|
self.lines = text.split("\n")
|
|
self.lines.reverse()
|
|
self.next_line_comments = []
|
|
|
|
def __next__(self):
|
|
# lineParser = LineParser(text.split('\n'))
|
|
|
|
self.next_line_comments = []
|
|
while len(self.lines) > 0:
|
|
line = self.lines.pop()
|
|
line = line.strip()
|
|
# if not line:
|
|
# continue
|
|
|
|
comment_index = line.find("//")
|
|
if comment_index >= 0:
|
|
comment = line[comment_index + len("//") :].strip()
|
|
line = line[:comment_index].strip()
|
|
if not line:
|
|
if comment:
|
|
self.next_line_comments.append(comment)
|
|
else:
|
|
if not line:
|
|
self.next_line_comments = []
|
|
|
|
if not line:
|
|
continue
|
|
|
|
# if args.verbose:
|
|
# print 'line:', line
|
|
|
|
return line
|
|
raise StopIteration()
|
|
|
|
|
|
def parse_enum(args, proto_file_path, parser, parent_context, enum_name):
|
|
|
|
# if args.verbose:
|
|
# print '# enum:', enum_name
|
|
|
|
context = EnumContext(args, parent_context, enum_name)
|
|
|
|
allow_alias = False
|
|
while True:
|
|
try:
|
|
line = next(parser)
|
|
except StopIteration:
|
|
raise Exception("Incomplete enum: %s" % proto_file_path)
|
|
|
|
if line == "option allow_alias = true;":
|
|
allow_alias = True
|
|
continue
|
|
|
|
if line == "}":
|
|
# if args.verbose:
|
|
# print
|
|
parent_context.enums.append(context)
|
|
return
|
|
|
|
if reserved_regex.search(line):
|
|
continue
|
|
|
|
item_match = enum_item_regex.search(line)
|
|
if item_match:
|
|
item_name = item_match.group(1).strip()
|
|
item_index = item_match.group(2).strip()
|
|
|
|
# if args.verbose:
|
|
# print '\t enum item[%s]: %s' % (item_index, item_name)
|
|
|
|
if item_name in context.item_names():
|
|
raise Exception(
|
|
"Duplicate enum name[%s]: %s" % (proto_file_path, item_name)
|
|
)
|
|
|
|
if item_index in context.item_indices():
|
|
if allow_alias:
|
|
continue
|
|
raise Exception(
|
|
"Duplicate enum index[%s]: %s" % (proto_file_path, item_name)
|
|
)
|
|
|
|
context.item_map[item_index] = item_name
|
|
|
|
continue
|
|
|
|
raise Exception('Invalid enum syntax[%s]: "%s"' % (proto_file_path, line))
|
|
|
|
|
|
def parse_oneof(args, proto_file_path, parser, parent_context, oneof_name):
|
|
|
|
# if args.verbose:
|
|
# print '# oneof:', oneof_name
|
|
|
|
if oneof_name in parent_context.field_names():
|
|
raise Exception(
|
|
"Duplicate message field name[%s]: %s" % (proto_file_path, oneof_name)
|
|
)
|
|
|
|
context = OneOfContext(args, parent_context, oneof_name)
|
|
|
|
oneof_index = None
|
|
|
|
while True:
|
|
try:
|
|
line = next(parser)
|
|
except StopIteration:
|
|
raise Exception("Incomplete oneof: %s" % proto_file_path)
|
|
|
|
if line == "}":
|
|
break
|
|
|
|
item_match = oneof_item_regex.search(line)
|
|
if item_match:
|
|
item_type = item_match.group(1).strip()
|
|
item_name = item_match.group(2).strip()
|
|
item_index = item_match.group(3).strip()
|
|
|
|
# if args.verbose:
|
|
# print '\t oneof item[%s]: %s' % (item_index, item_name)
|
|
|
|
if item_name in context.item_names():
|
|
raise Exception(
|
|
"Duplicate oneof name[%s]: %s" % (proto_file_path, item_name)
|
|
)
|
|
|
|
if item_index in context.item_indices():
|
|
raise Exception(
|
|
"Duplicate oneof index[%s]: %s" % (proto_file_path, item_name)
|
|
)
|
|
|
|
context.item_type_map[item_name] = item_type
|
|
context.item_index_map[item_index] = item_name
|
|
|
|
continue
|
|
|
|
raise Exception('Invalid oneof syntax[%s]: "%s"' % (proto_file_path, line))
|
|
|
|
parent_context.oneofs.append(context)
|
|
return context
|
|
|
|
|
|
def optional_match_group(match, index):
|
|
group = match.group(index)
|
|
if group is None:
|
|
return None
|
|
return group.strip()
|
|
|
|
|
|
def parse_message(args, proto_file_path, parser, parent_context, message_name):
|
|
|
|
# if args.verbose:
|
|
# print '# message:', message_name
|
|
|
|
context = MessageContext(args, parent_context, message_name)
|
|
|
|
sort_index = 0
|
|
while True:
|
|
try:
|
|
line = next(parser)
|
|
except StopIteration:
|
|
raise Exception("Incomplete message: %s" % proto_file_path)
|
|
|
|
field_comments = parser.next_line_comments
|
|
|
|
if line == "}":
|
|
# if args.verbose:
|
|
# print
|
|
parent_context.messages.append(context)
|
|
return
|
|
|
|
enum_match = enum_regex.search(line)
|
|
if enum_match:
|
|
enum_name = enum_match.group(1).strip()
|
|
parse_enum(args, proto_file_path, parser, context, enum_name)
|
|
continue
|
|
|
|
message_match = message_regex.search(line)
|
|
if message_match:
|
|
message_name = message_match.group(1).strip()
|
|
parse_message(args, proto_file_path, parser, context, message_name)
|
|
continue
|
|
|
|
if proto_syntax == "proto3":
|
|
oneof_match = oneof_regex.search(line)
|
|
if oneof_match:
|
|
oneof_name = oneof_match.group(1).strip()
|
|
oneof_context = parse_oneof(
|
|
args, proto_file_path, parser, context, oneof_name
|
|
)
|
|
oneof_index = oneof_context.last_index()
|
|
oneof_type = oneof_context.derive_swift_name()
|
|
context.field_map[oneof_index] = MessageField(
|
|
oneof_name,
|
|
oneof_index,
|
|
"optional",
|
|
oneof_type,
|
|
None,
|
|
sort_index,
|
|
False,
|
|
)
|
|
sort_index = sort_index + 1
|
|
continue
|
|
|
|
if reserved_regex.search(line):
|
|
continue
|
|
|
|
# Examples:
|
|
#
|
|
# optional bytes id = 1;
|
|
# optional bool isComplete = 2 [default = false];
|
|
#
|
|
# NOTE: optional and required are not valid in proto3.
|
|
item_match = message_item_regex.search(line)
|
|
if item_match:
|
|
# print 'item_rules:', item_match.groups()
|
|
item_rules = optional_match_group(item_match, 1)
|
|
item_type = optional_match_group(item_match, 2)
|
|
item_name = optional_match_group(item_match, 3)
|
|
item_index = optional_match_group(item_match, 4)
|
|
# item_defaults_1 = optional_match_group(item_match, 5)
|
|
item_default = optional_match_group(item_match, 6)
|
|
|
|
if proto_syntax == "proto3":
|
|
if item_rules is None:
|
|
item_rules = "optional"
|
|
elif item_rules == "repeated":
|
|
pass
|
|
else:
|
|
raise Exception(
|
|
"Unexpected rule[%s]: %s" % (proto_file_path, item_rules)
|
|
)
|
|
|
|
# print 'item_rules:', item_rules
|
|
# print 'item_type:', item_type
|
|
# print 'item_name:', item_name
|
|
# print 'item_index:', item_index
|
|
# print 'item_default:', item_default
|
|
|
|
message_field = {
|
|
"rules": item_rules,
|
|
"type": item_type,
|
|
"name": item_name,
|
|
"index": item_index,
|
|
"default": item_default,
|
|
"field_comments": field_comments,
|
|
}
|
|
# print 'message_field:', message_field
|
|
|
|
# if args.verbose:
|
|
# print '\t message field[%s]: %s' % (item_index, str(message_field))
|
|
|
|
if item_name in context.field_names():
|
|
raise Exception(
|
|
"Duplicate message field name[%s]: %s"
|
|
% (proto_file_path, item_name)
|
|
)
|
|
# context.field_names.add(item_name)
|
|
|
|
if item_index in context.field_indices():
|
|
raise Exception(
|
|
"Duplicate message field index[%s]: %s"
|
|
% (proto_file_path, item_name)
|
|
)
|
|
# context.field_indices.add(item_index)
|
|
|
|
is_required = "@required" in field_comments
|
|
# if is_required:
|
|
# print 'is_required:', item_name
|
|
# print 'item_name:', item_name, 'item_type:', item_type
|
|
|
|
context.field_map[item_index] = MessageField(
|
|
item_name,
|
|
item_index,
|
|
item_rules,
|
|
item_type,
|
|
item_default,
|
|
sort_index,
|
|
is_required,
|
|
)
|
|
|
|
sort_index = sort_index + 1
|
|
|
|
continue
|
|
|
|
raise Exception("Invalid message syntax[%s]: %s" % (proto_file_path, line))
|
|
|
|
|
|
def process_proto_file(args, proto_file_path, dst_file_path):
|
|
with open(proto_file_path, "rt") as f:
|
|
text = f.read()
|
|
|
|
text = multiline_comment_regex.sub("", text)
|
|
|
|
parser = LineParser(text)
|
|
|
|
# lineParser = LineParser(text.split('\n'))
|
|
|
|
context = FileContext(args)
|
|
|
|
while True:
|
|
try:
|
|
line = next(parser)
|
|
except StopIteration:
|
|
break
|
|
|
|
enum_match = enum_regex.search(line)
|
|
if enum_match:
|
|
enum_name = enum_match.group(1).strip()
|
|
parse_enum(args, proto_file_path, parser, context, enum_name)
|
|
continue
|
|
|
|
syntax_match = syntax_regex.search(line)
|
|
if syntax_match:
|
|
global proto_syntax
|
|
proto_syntax = syntax_match.group(1).strip()
|
|
if args.verbose:
|
|
print("Syntax:", proto_syntax)
|
|
continue
|
|
|
|
if option_regex.search(line):
|
|
if args.verbose:
|
|
print("# Ignoring option")
|
|
continue
|
|
|
|
package_match = package_regex.search(line)
|
|
if package_match:
|
|
if args.package:
|
|
raise Exception("More than one package statement: %s" % proto_file_path)
|
|
args.package = package_match.group(1).strip()
|
|
|
|
if args.verbose:
|
|
print("# package:", args.package)
|
|
continue
|
|
|
|
message_match = message_regex.search(line)
|
|
if message_match:
|
|
message_name = message_match.group(1).strip()
|
|
parse_message(args, proto_file_path, parser, context, message_name)
|
|
continue
|
|
|
|
raise Exception("Invalid syntax[%s]: %s" % (proto_file_path, line))
|
|
|
|
writer = LineWriter(args)
|
|
context.prepare()
|
|
context.generate(writer)
|
|
output = writer.join()
|
|
with open(dst_file_path, "wt") as f:
|
|
f.write(output)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description="Protocol Buffer Swift Wrapper Generator."
|
|
)
|
|
parser.add_argument("--proto-dir", help="dir path of the proto schema file.")
|
|
parser.add_argument("--proto-file", help="filename of the proto schema file.")
|
|
parser.add_argument("--wrapper-prefix", help="name prefix for generated wrappers.")
|
|
parser.add_argument("--proto-prefix", help="name prefix for proto bufs.")
|
|
parser.add_argument("--dst-dir", help="path to the destination directory.")
|
|
parser.add_argument(
|
|
"--verbose", action="store_true", help="enables verbose logging"
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
if args.verbose:
|
|
print("args:", args)
|
|
|
|
proto_file_path = os.path.abspath(os.path.join(args.proto_dir, args.proto_file))
|
|
if not os.path.exists(proto_file_path):
|
|
raise Exception("File does not exist: %s" % proto_file_path)
|
|
|
|
dst_dir_path = os.path.abspath(args.dst_dir)
|
|
if not os.path.exists(dst_dir_path):
|
|
raise Exception("Destination does not exist: %s" % dst_dir_path)
|
|
|
|
dst_file_path = os.path.join(dst_dir_path, "%s.swift" % args.wrapper_prefix)
|
|
|
|
if args.verbose:
|
|
print("dst_file_path:", dst_file_path)
|
|
|
|
args.package = None
|
|
process_proto_file(args, proto_file_path, dst_file_path)
|
|
|
|
# print 'complete.'
|