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)