···11+#!/usr/bin/env swift
22+/*
33+This file is part of Darling.
44+55+Copyright (C) 2017 Lubos Dolezel
66+77+Darling is free software: you can redistribute it and/or modify
88+it under the terms of the GNU General Public License as published by
99+the Free Software Foundation, either version 3 of the License, or
1010+(at your option) any later version.
1111+1212+Darling is distributed in the hope that it will be useful,
1313+but WITHOUT ANY WARRANTY; without even the implied warranty of
1414+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1515+GNU General Public License for more details.
1616+1717+You should have received a copy of the GNU General Public License
1818+along with Darling. If not, see <http://www.gnu.org/licenses/>.
1919+*/
2020+2121+import Foundation
2222+2323+guard CommandLine.arguments.count == 3 else {
2424+ fatalError("Usage: \(CommandLine.arguments[0]) <Objective-C binary> <output directory>")
2525+}
2626+2727+let copyright = "/*\n" +
2828+ "This file is part of Darling.\n" +
2929+ "\n" +
3030+3131+ "Copyright (C) 2017 Lubos Dolezel\n" +
3232+ "\n" +
3333+3434+ "Darling is free software: you can redistribute it and/or modify\n" +
3535+ "it under the terms of the GNU General Public License as published by\n" +
3636+ "the Free Software Foundation, either version 3 of the License, or\n" +
3737+ "(at your option) any later version.\n" +
3838+ "\n" +
3939+4040+ "Darling is distributed in the hope that it will be useful,\n" +
4141+ "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" +
4242+ "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" +
4343+ "GNU General Public License for more details.\n" +
4444+ "\n" +
4545+4646+ "You should have received a copy of the GNU General Public License\n" +
4747+ "along with Darling. If not, see <http://www.gnu.org/licenses/>.\n" +
4848+ "*/\n\n"
4949+5050+let name = CommandLine.arguments[1]
5151+let outputDirectory = CommandLine.arguments[2]
5252+5353+// So we can generate a "master header"
5454+var moduleName = (name as NSString).pathComponents.last! as String
5555+5656+// Set up the environment for class-dump
5757+let pipe = Pipe()
5858+5959+let classDump = Process()
6060+classDump.launchPath = "/bin/bash"
6161+classDump.arguments = ["-c","class-dump \(name)"]
6262+classDump.standardOutput = pipe
6363+6464+// Redirect the output
6565+let outHandle = pipe.fileHandleForReading
6666+6767+var output = ""
6868+6969+// Put the output in the string output
7070+outHandle.readabilityHandler = { pipe in
7171+ if let line = String(data: pipe.availableData, encoding: String.Encoding.utf8) {
7272+ output.append(line)
7373+ } else {
7474+ print("Error decoding data: \(pipe.availableData)")
7575+ }
7676+}
7777+7878+// Run it and wait for it to finish
7979+classDump.launch()
8080+classDump.waitUntilExit()
8181+8282+var classes: [(class: String, superclass: String)] = []
8383+8484+// Begin ugly string parsing code
8585+while output.contains("@interface") {
8686+ let interfaceRange = output.range(of: "@interface")!
8787+ output.removeSubrange(output.startIndex ... interfaceRange.upperBound)
8888+ if let space = output.range(of: " : ") {
8989+ let className = output[output.startIndex ..< space.lowerBound]
9090+9191+ output.removeSubrange(output.startIndex ..< space.upperBound)
9292+9393+ var endOfSuperclass: String.CharacterView.Index!
9494+ let nextNewline = output.range(of: "\n")
9595+ let nextProtocolConformance = output.range(of: " <")
9696+ if nextNewline != nil && nextProtocolConformance != nil {
9797+ endOfSuperclass = nextNewline!.lowerBound < nextProtocolConformance!.lowerBound ? nextNewline!.lowerBound : nextProtocolConformance!.lowerBound
9898+ } else if nextNewline != nil {
9999+ endOfSuperclass = nextNewline!.lowerBound
100100+ } else if nextProtocolConformance != nil {
101101+ endOfSuperclass = nextProtocolConformance!.lowerBound
102102+ } else {
103103+ fatalError("Failed to detect superclass: \(className)")
104104+ }
105105+ let superclass = output[output.startIndex ..< endOfSuperclass]
106106+107107+ classes.append((class: className, superclass: superclass))
108108+ }
109109+}
110110+// End ugly string parsing code
111111+112112+// Create destination folders if they don't exist
113113+try FileManager.default.createDirectory(atPath: outputDirectory, withIntermediateDirectories: true)
114114+try FileManager.default.createDirectory(atPath: outputDirectory + "/Headers", withIntermediateDirectories: true)
115115+try FileManager.default.createDirectory(atPath: outputDirectory + "/Sources", withIntermediateDirectories: true)
116116+117117+// Emit master header
118118+var mh = copyright
119119+mh += "#import <Foundation/Foundation.h>\n"
120120+121121+// Generate headers and sources for each class
122122+for var classEntry in classes {
123123+124124+ mh += "#import <\(moduleName)/\(classEntry.class).h>\n"
125125+126126+ var header = copyright
127127+128128+ header += "@interface \(classEntry.class) : \(classEntry.superclass)\n\n"
129129+130130+ header += "@end\n"
131131+132132+ var implementation = copyright
133133+134134+ implementation += "#import <\(moduleName)/\(moduleName).h>\n\n"
135135+136136+ implementation += "@implementation \(classEntry.class)\n\n"
137137+138138+ implementation += "- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {\n"
139139+ implementation += " return [NSMethodSignature signatureWithObjCTypes: \"v@:\"];\n"
140140+ implementation += "}\n\n"
141141+142142+ implementation += "- (void)forwardInvocation:(NSInvocation *)anInvocation {\n"
143143+ implementation += " NSLog(@\"Stub called: %@ in %@\", NSStringFromSelector([anInvocation selector]), [self class]);\n"
144144+ implementation += "}\n\n"
145145+146146+ implementation += "@end\n"
147147+148148+ try header.write(toFile: outputDirectory + "/Headers/" + classEntry.class + ".h", atomically: false, encoding: String.Encoding.utf8)
149149+ try implementation.write(toFile: outputDirectory + "/Sources/" + classEntry.class + ".m", atomically: false, encoding: String.Encoding.utf8)
150150+}
151151+152152+try mh.write(toFile: outputDirectory + "/Headers/" + moduleName + ".h", atomically: false, encoding: String.Encoding.utf8)