this repo has no description
1#!/usr/bin/env swift
2/*
3This file is part of Darling.
4
5Copyright (C) 2017 Lubos Dolezel
6
7Darling is free software: you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation, either version 3 of the License, or
10(at your option) any later version.
11
12Darling is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU General Public License
18along with Darling. If not, see <http://www.gnu.org/licenses/>.
19*/
20
21import Foundation
22
23guard CommandLine.arguments.count == 3 else {
24 fatalError("Usage: \(CommandLine.arguments[0]) <Objective-C binary> <output directory>")
25}
26
27let copyright = "/*\n" +
28 "This file is part of Darling.\n" +
29 "\n" +
30
31 "Copyright (C) 2018 Lubos Dolezel\n" +
32 "\n" +
33
34 "Darling is free software: you can redistribute it and/or modify\n" +
35 "it under the terms of the GNU General Public License as published by\n" +
36 "the Free Software Foundation, either version 3 of the License, or\n" +
37 "(at your option) any later version.\n" +
38 "\n" +
39
40 "Darling is distributed in the hope that it will be useful,\n" +
41 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" +
42 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" +
43 "GNU General Public License for more details.\n" +
44 "\n" +
45
46 "You should have received a copy of the GNU General Public License\n" +
47 "along with Darling. If not, see <http://www.gnu.org/licenses/>.\n" +
48 "*/\n\n"
49
50let name = CommandLine.arguments[1]
51let outputDirectory = CommandLine.arguments[2]
52
53// So we can generate a "master header"
54var moduleName = (name as NSString).pathComponents.last! as String
55
56// Set up the environment for class-dump
57let pipe = Pipe()
58
59let classDump = Process()
60classDump.launchPath = "/bin/bash"
61classDump.arguments = ["-c","class-dump \(name)"]
62classDump.standardOutput = pipe
63
64// Redirect the output
65let outHandle = pipe.fileHandleForReading
66
67var output = ""
68
69// Put the output in the string output
70outHandle.readabilityHandler = { pipe in
71 if let line = String(data: pipe.availableData, encoding: String.Encoding.utf8) {
72 output.append(line)
73 } else {
74 print("Error decoding data: \(pipe.availableData)")
75 }
76}
77
78// Run it and wait for it to finish
79classDump.launch()
80classDump.waitUntilExit()
81
82var classes: [(class: String, superclass: String)] = []
83
84// Begin ugly string parsing code
85while output.contains("@interface") {
86 let interfaceRange = output.range(of: "@interface")!
87 output.removeSubrange(output.startIndex ... interfaceRange.upperBound)
88 if let space = output.range(of: " : ") {
89 let className = output[output.startIndex ..< space.lowerBound]
90
91 output.removeSubrange(output.startIndex ..< space.upperBound)
92
93 var endOfSuperclass: String.Index!
94 let nextNewline = output.range(of: "\n")
95 let nextProtocolConformance = output.range(of: " <")
96 if nextNewline != nil && nextProtocolConformance != nil {
97 endOfSuperclass = nextNewline!.lowerBound < nextProtocolConformance!.lowerBound ? nextNewline!.lowerBound : nextProtocolConformance!.lowerBound
98 } else if nextNewline != nil {
99 endOfSuperclass = nextNewline!.lowerBound
100 } else if nextProtocolConformance != nil {
101 endOfSuperclass = nextProtocolConformance!.lowerBound
102 } else {
103 fatalError("Failed to detect superclass: \(className)")
104 }
105 let superclass = output[output.startIndex ..< endOfSuperclass]
106
107 classes.append((class: String(className), superclass: String(superclass)))
108 }
109}
110// End ugly string parsing code
111
112// Create destination folders if they don't exist
113try FileManager.default.createDirectory(atPath: outputDirectory, withIntermediateDirectories: true)
114try FileManager.default.createDirectory(atPath: outputDirectory + "/Headers", withIntermediateDirectories: true)
115try FileManager.default.createDirectory(atPath: outputDirectory + "/Sources", withIntermediateDirectories: true)
116
117// Emit master header
118var mh = copyright
119mh += "#import <Foundation/Foundation.h>\n"
120
121// Generate headers and sources for each class
122for classEntry in classes {
123
124 mh += "#import <\(moduleName)/\(classEntry.class).h>\n"
125
126 var header = copyright
127
128 header += "@interface \(classEntry.class) : \(classEntry.superclass)\n\n"
129
130 header += "@end\n"
131
132 var implementation = copyright
133
134 implementation += "#import <\(moduleName)/\(moduleName).h>\n\n"
135
136 implementation += "@implementation \(classEntry.class)\n\n"
137
138 implementation += "- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {\n"
139 implementation += " return [NSMethodSignature signatureWithObjCTypes: \"v@:\"];\n"
140 implementation += "}\n\n"
141
142 implementation += "- (void)forwardInvocation:(NSInvocation *)anInvocation {\n"
143 implementation += " NSLog(@\"Stub called: %@ in %@\", NSStringFromSelector([anInvocation selector]), [self class]);\n"
144 implementation += "}\n\n"
145
146 implementation += "@end\n"
147
148 try header.write(toFile: outputDirectory + "/Headers/" + classEntry.class + ".h", atomically: false, encoding: String.Encoding.utf8)
149 try implementation.write(toFile: outputDirectory + "/Sources/" + classEntry.class + ".m", atomically: false, encoding: String.Encoding.utf8)
150}
151
152try mh.write(toFile: outputDirectory + "/Headers/" + moduleName + ".h", atomically: false, encoding: String.Encoding.utf8)