uberpoet/filegen.py (199 lines of code) (raw):

# Copyright (c) 2018 Uber Technologies, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import from .util import first_in_dict, seed uber_poet_header = """ // This code was @generated by Uber Poet, a mock application generator. // Check it out at https://github.com/uber/uber-poet """ # This method can only be invoked between Swift modules as it is not # ObjC friendly due to the use of generics. swift_func_template = """ public func complexCrap{0}<T>(arg: Int, stuff:T) -> Int {{ let a = Int(4 * {1} + Int(Float(arg) / 32.0)) let b = Int(4 * {1} + Int(Float(arg) / 32.0)) let c = Int(4 * {1} + Int(Float(arg) / 32.0)) return Int(4 * {1} + Int(Float(arg) / 32.0)) + a + b + c }}""" swift_func_objc_friendly_template = """ @objc public func complexStuff{0}(arg: String) -> String {{ let randomString = NSUUID().uuidString return ("\\(arg)-\\(randomString)") }} """ swift_class_template = """ @objc public class MyClass{0}: NSObject {{ public let x: Int public let y: String @objc public override init() {{ x = 7 y = "hi" {2} }} {1} }}""" swift_to_swift_func_call_template = """MyClass{0}().complexCrap{1}(arg: 4, stuff: 2)""" swift_to_objc_func_call_template = """MyClass_{0}().complexCrap{1}(4, stuff: \"2\")""" swift_to_swift_objc_friendly_func_call_template = """MyClass{0}().complexStuff{1}(arg: \"4\")""" swift_to_objc_friendly_func_call_template = """MyClass_{0}().complexStuff{1}(arg: \"4\")""" objc_to_swift_func_call_template = """[[[MyClass{} alloc] init] complexStuff{}WithArg:@\"4\"];""" objc_to_objc_func_call_template = """[[[MyClass_{} alloc] init] complexCrap{}:4 stuff:@\"2\"];""" objc_header_func_template = """- (int)complexCrap{0}:(int)arg stuff:(nonnull NSString *)stuff;""" objc_source_func_template = """ - (int)complexCrap{0}:(int)arg stuff:(nonnull NSString *)stuff; {{ int a = (int)(4 * self.{1} + (int)((float)arg / 32.0)); int b = (int)(4 * self.{1} + (int)((float)arg / 32.0)); int c = (int)(4 * self.{1} + (int)((float)arg / 32.0)); return (int)(4 * self.{1} + (int)((float)arg / 32.0)) + a + b + c; }}""" objc_system_import_template = """ @import Foundation; """ objc_header_template = """ @interface MyClass_{0} : NSObject @property(nonatomic, readonly) int x; @property(nonatomic, readonly, nonnull) NSString *y; - (nonnull instancetype)initWithX:(int)x y:(nonnull NSString *)y; {1} @end """ objc_source_template = """ @implementation MyClass_{0} - (nonnull instancetype)initWithX:(int)x y:(nonnull NSString *)y; {{ self = [super init]; NSParameterAssert(self); _x = x; _y = y; {2} return self; }} {1} @end """ def get_func_call_template(from_language, to_language, function_type): if function_type == FuncType.SWIFT_ONLY: if from_language == Language.OBJC: raise ValueError("Cannot invoke SWIFT_ONLY method from ObjC!") return swift_to_swift_func_call_template if to_language == Language.SWIFT else swift_to_objc_func_call_template elif function_type == FuncType.OBJC_FRIENDLY: if from_language == Language.SWIFT: if to_language == Language.SWIFT: return swift_to_swift_objc_friendly_func_call_template elif to_language == Language.OBJC: return swift_to_objc_friendly_func_call_template elif from_language == Language.OBJC: if to_language == Language.SWIFT: return objc_to_swift_func_call_template elif to_language == Language.OBJC: return objc_to_objc_func_call_template def get_import_func_calls(from_language, import_list, indent=0): out = [] for i in import_list: if type(i) is str: continue module = first_in_dict(i) to_language = module["language"] for file_result in module["files"].values(): for class_num, class_funcs in file_result.classes.items(): for func_type, func_nums in class_funcs.items(): for func_num in func_nums: if (func_type == FuncType.SWIFT_ONLY and from_language == Language.OBJC and to_language == Language.SWIFT): # We cannot invoke Swift only functions from ObjC since they use generics. continue text = get_func_call_template(from_language, to_language, func_type).format(class_num, func_num) indented_text = '\n'.join(" " * indent + line for line in text.splitlines()) out.append(indented_text) return "\n".join(out) class Language(object): SWIFT = 'Swift' OBJC = 'Objective-C' @staticmethod def enum_list(): return [Language.SWIFT, Language.OBJC] class FuncType(object): """ Describes the type of function added to a class. Helps distinguish how to invoke a function between modules. """ SWIFT_ONLY = 'swift_only' OBJC_FRIENDLY = 'objc_friendly' class FileResult(object): def __init__(self, text, functions, classes): super(FileResult, self).__init__() self.text = text # string self.text_line_count = len(text.split('\n')) self.functions = functions # list of indexes self.classes = classes # {class index: {func type: function indexes}} def __str__(self): return "<text_line_count : {} functions : {} classes : {}>".format(self.text_line_count, self.functions, self.classes) class FileGenerator(object): def gen_file(self, class_count, function_count): return FileResult("", [], {}) class ObjCHeaderFileGenerator(FileGenerator): @staticmethod def language(): return Language.OBJC @staticmethod def extension(): return '.h' @staticmethod def gen_func(nums): out = [] for num in nums: out.append(objc_header_func_template.format(num)) return "\n".join(out), nums def get_header(self, objc_class): out = [] class_nums = {} for c in objc_class.classes: num = c func_out, func_nums = self.gen_func(objc_class.classes[c][FuncType.OBJC_FRIENDLY]) out.append(objc_header_template.format(num, func_out)) class_nums[num] = {FuncType.OBJC_FRIENDLY: func_nums} return "\n".join(out), class_nums def gen_file(self, objc_class): class_out, class_nums = self.get_header(objc_class) chunks = [uber_poet_header, objc_system_import_template, class_out] return FileResult("\n".join(chunks), [], class_nums) class ObjCSourceFileGenerator(FileGenerator): @staticmethod def language(): return Language.OBJC @staticmethod def extension(): return '.m' @staticmethod def gen_func(function_count, var_name): out = [] nums = [] for _ in xrange(function_count): num = seed() text = objc_source_func_template.format(num, var_name) nums.append(num) out.append(text) return "\n".join(out), nums def gen_class(self, class_count, func_per_class_count, import_list): out = [] class_nums = {} for _ in xrange(class_count): num = seed() func_out, func_nums = self.gen_func(func_per_class_count, "x") func_call_out = get_import_func_calls(self.language(), import_list, indent=4) out.append(objc_source_template.format(num, func_out, func_call_out)) class_nums[num] = {FuncType.OBJC_FRIENDLY: func_nums} return "\n".join(out), class_nums def gen_file(self, class_count, function_count, import_list=None): if import_list is None: import_list = [] imports = [] for i in import_list: if type(i) is str: imports.append('#import \"{}\"'.format(i)) elif type(i) is dict: imports.append('@import {};'.format(i.keys()[0])) imports_out = "\n".join(imports) class_out, class_nums = self.gen_class(class_count, 5, import_list) chunks = [uber_poet_header, objc_system_import_template, imports_out, class_out] return FileResult("\n".join(chunks), [], class_nums) class SwiftFileGenerator(FileGenerator): def __init__(self): self.gen_state = {} @staticmethod def language(): return Language.SWIFT @staticmethod def extension(): return '.swift' @staticmethod def gen_func(function_count, var_name, indent=0): out = [] nums = [] for _ in xrange(function_count): num = seed() text = swift_func_template.format(num, var_name) indented_text = '\n'.join(" " * indent + line for line in text.splitlines()) nums.append(num) out.append(indented_text) return "\n".join(out), nums @staticmethod def gen_objc_friendly_func(indent=0): out = [] nums = [] num = seed() text = swift_func_objc_friendly_template.format(num) indented_text = '\n'.join(" " * indent + line for line in text.splitlines()) nums.append(num) out.append(indented_text) return "\n".join(out), nums def gen_class(self, class_count, func_per_class_count, import_list): out = [] class_nums = {} for _ in xrange(class_count): num = seed() swift_only_func_out, swift_only_func_nums = self.gen_func(func_per_class_count, "x", indent=4) swift_objc_friendly_func_out, swift_objc_friendly_func_nums = self.gen_objc_friendly_func(indent=4) func_out = swift_only_func_out + "\n" + swift_objc_friendly_func_out func_call_out = get_import_func_calls(self.language(), import_list, indent=8) out.append(swift_class_template.format(num, func_out, func_call_out)) class_nums[num] = { FuncType.SWIFT_ONLY: swift_only_func_nums, FuncType.OBJC_FRIENDLY: swift_objc_friendly_func_nums } return "\n".join(out), class_nums def gen_file(self, class_count, function_count, import_list=None): if import_list is None: import_list = [] imports_out = "\n".join(["import {}".format(i if type(i) is str else i.keys()[0]) for i in import_list]) func_out, func_nums = self.gen_func(function_count, "7") class_out, class_nums = self.gen_class(class_count, 5, import_list) chunks = [uber_poet_header, imports_out, func_out, class_out] return FileResult("\n".join(chunks), func_nums, class_nums) @staticmethod def gen_main(template, importing_module_name, class_num, func_num, to_language): import_line = 'import {}'.format(importing_module_name) action_expr = get_func_call_template(Language.SWIFT, to_language, FuncType.SWIFT_ONLY).format( class_num, func_num) print_line = 'print("\\({})")'.format(action_expr) return template.format(uber_poet_header, import_line, print_line)