git.iko.soy/SourceDocsSwift
on master help sign in
Initial commit
17dbfa5ilyakooo0
.gitignorenew file+78
@@ -0,0 +1,78 @@
1+# Xcode
2+#
3+# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4+
5+## Build generated
6+build/
7+DerivedData/
8+
9+## Various settings
10+*.pbxuser
11+!default.pbxuser
12+*.mode1v3
13+!default.mode1v3
14+*.mode2v3
15+!default.mode2v3
16+*.perspectivev3
17+!default.perspectivev3
18+xcuserdata/
19+
20+## Other
21+*.moved-aside
22+*.xccheckout
23+*.xcscmblueprint
24+
25+## Obj-C/Swift specific
26+*.hmap
27+*.ipa
28+*.dSYM.zip
29+*.dSYM
30+
31+## Playgrounds
32+timeline.xctimeline
33+playground.xcworkspace
34+
35+# Swift Package Manager
36+#
37+# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38+# Packages/
39+# Package.pins
40+# Package.resolved
41+.build/
42+
43+# CocoaPods
44+#
45+# We recommend against adding the Pods directory to your .gitignore. However
46+# you should judge for yourself, the pros and cons are mentioned at:
47+# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
48+#
49+# Pods/
50+#
51+# Add this line if you want to avoid checking in source code from the Xcode workspace
52+# *.xcworkspace
53+
54+# Carthage
55+#
56+# Add this line if you want to avoid checking in source code from Carthage dependencies.
57+# Carthage/Checkouts
58+
59+Carthage/Build
60+
61+# fastlane
62+#
63+# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
64+# screenshots whenever they are needed.
65+# For more information about the recommended setup visit:
66+# https://docs.fastlane.tools/best-practices/source-control/#source-control
67+
68+fastlane/report.xml
69+fastlane/Preview.html
70+fastlane/screenshots/**/*.png
71+fastlane/test_output
72+
73+# Code Injection
74+#
75+# After new code Injection tools there's a generated folder /iOSInjectionProject
76+# https://github.com/johnno1962/injectionforxcode
77+
78+iOSInjectionProject/
README.mdnew file+3
@@ -0,0 +1,3 @@
1+# SourceDocsSwift
2+
3+Генернирует LaTeX-таблицы всех классов, структур и их полей и методов из проекта Xcode для вставки в техническую документацию.
SourceDocsSwift.xcodeproj/project.pbxproj+18
@@ -7,7 +7,11 @@
7 objects = {
7 objects = {
8
8
9/* Begin PBXBuildFile section */
9/* Begin PBXBuildFile section */
10+ 094C8925212F6FAF00993F4A /* XcodeBuildTargetResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094C8924212F6FAF00993F4A /* XcodeBuildTargetResponse.swift */; };
11+ 094C8927212F6FE900993F4A /* Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094C8926212F6FE900993F4A /* Shell.swift */; };
10 09D361FA212DD5490010CC83 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D361F9212DD5490010CC83 /* main.swift */; };
12 09D361FA212DD5490010CC83 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D361F9212DD5490010CC83 /* main.swift */; };
13+ 09D36201212DD58E0010CC83 /* SourceKittenResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D36200212DD58E0010CC83 /* SourceKittenResponse.swift */; };
14+ 09D36203212E13820010CC83 /* TeXer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D36202212E13820010CC83 /* TeXer.swift */; };
11/* End PBXBuildFile section */
15/* End PBXBuildFile section */
12
16
13/* Begin PBXCopyFilesBuildPhase section */
17/* Begin PBXCopyFilesBuildPhase section */
@@ -23,8 +27,13 @@
23/* End PBXCopyFilesBuildPhase section */
27/* End PBXCopyFilesBuildPhase section */
24
28
25/* Begin PBXFileReference section */
29/* Begin PBXFileReference section */
30+ 094C8924212F6FAF00993F4A /* XcodeBuildTargetResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeBuildTargetResponse.swift; sourceTree = "<group>"; };
31+ 094C8926212F6FE900993F4A /* Shell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shell.swift; sourceTree = "<group>"; };
32+ 09B928F221304ED6005A0427 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
26 09D361F6212DD5480010CC83 /* SourceDocsSwift */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = SourceDocsSwift; sourceTree = BUILT_PRODUCTS_DIR; };
33 09D361F6212DD5480010CC83 /* SourceDocsSwift */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = SourceDocsSwift; sourceTree = BUILT_PRODUCTS_DIR; };
27 09D361F9212DD5490010CC83 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
34 09D361F9212DD5490010CC83 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
35+ 09D36200212DD58E0010CC83 /* SourceKittenResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceKittenResponse.swift; sourceTree = "<group>"; };
36+ 09D36202212E13820010CC83 /* TeXer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeXer.swift; sourceTree = "<group>"; };
28/* End PBXFileReference section */
37/* End PBXFileReference section */
29
38
30/* Begin PBXFrameworksBuildPhase section */
39/* Begin PBXFrameworksBuildPhase section */
@@ -41,6 +50,7 @@
41 09D361ED212DD5480010CC83 = {
50 09D361ED212DD5480010CC83 = {
42 isa = PBXGroup;
51 isa = PBXGroup;
43 children = (
52 children = (
53+ 09B928F221304ED6005A0427 /* README.md */,
44 09D361F8212DD5480010CC83 /* SourceDocsSwift */,
54 09D361F8212DD5480010CC83 /* SourceDocsSwift */,
45 09D361F7212DD5480010CC83 /* Products */,
55 09D361F7212DD5480010CC83 /* Products */,
46 );
56 );
@@ -58,6 +68,10 @@
58 isa = PBXGroup;
68 isa = PBXGroup;
59 children = (
69 children = (
60 09D361F9212DD5490010CC83 /* main.swift */,
70 09D361F9212DD5490010CC83 /* main.swift */,
71+ 094C8926212F6FE900993F4A /* Shell.swift */,
72+ 094C8924212F6FAF00993F4A /* XcodeBuildTargetResponse.swift */,
73+ 09D36202212E13820010CC83 /* TeXer.swift */,
74+ 09D36200212DD58E0010CC83 /* SourceKittenResponse.swift */,
61 );
75 );
62 path = SourceDocsSwift;
76 path = SourceDocsSwift;
63 sourceTree = "<group>";
77 sourceTree = "<group>";
@@ -119,6 +133,10 @@
119 isa = PBXSourcesBuildPhase;
133 isa = PBXSourcesBuildPhase;
120 buildActionMask = 2147483647;
134 buildActionMask = 2147483647;
121 files = (
135 files = (
136+ 09D36201212DD58E0010CC83 /* SourceKittenResponse.swift in Sources */,
137+ 09D36203212E13820010CC83 /* TeXer.swift in Sources */,
138+ 094C8927212F6FE900993F4A /* Shell.swift in Sources */,
139+ 094C8925212F6FAF00993F4A /* XcodeBuildTargetResponse.swift in Sources */,
122 09D361FA212DD5490010CC83 /* main.swift in Sources */,
140 09D361FA212DD5490010CC83 /* main.swift in Sources */,
123 );
141 );
124 runOnlyForDeploymentPostprocessing = 0;
142 runOnlyForDeploymentPostprocessing = 0;
SourceDocsSwift.xcodeproj/xcuserdata/ilya.xcuserdatad/xcschemes/xcschememanagement.plist+8
@@ -10,5 +10,13 @@
10 <integer>0</integer>
10 <integer>0</integer>
11 </dict>
11 </dict>
12 </dict>
12 </dict>
13+ <key>SuppressBuildableAutocreation</key>
14+ <dict>
15+ <key>09D361F5212DD5480010CC83</key>
16+ <dict>
17+ <key>primary</key>
18+ <true/>
19+ </dict>
20+ </dict>
13</dict>
21</dict>
14</plist>
22</plist>
SourceDocsSwift/Shell.swiftnew file+41
@@ -0,0 +1,41 @@
1+//
2+// Shell.swift
3+// SourceDocsSwift
4+//
5+// Created by Ilya Kos on 8/24/18.
6+// Copyright © 2018 Ilya Kos. All rights reserved.
7+//
8+
9+import Foundation
10+
11+struct Shell {
12+ static func launch(executable: String, arguments: [String], launchDirectory: String?) -> Data? {
13+ let process = Process()
14+ if let launchDirectory = launchDirectory {
15+ process.currentDirectoryPath = launchDirectory
16+ }
17+ process.arguments = arguments
18+ if !FileManager.default.isExecutableFile(atPath: executable) {
19+ return nil
20+ }
21+ let executableURL = URL(fileURLWithPath: executable)
22+ process.executableURL = executableURL
23+
24+ let pipe = Pipe()
25+
26+ var out = Data()
27+
28+ NotificationCenter.default.addObserver(forName: NSNotification.Name.NSFileHandleDataAvailable, object: pipe.fileHandleForReading, queue: nil) { (_) in
29+ out.append(pipe.fileHandleForReading.readDataToEndOfFile())
30+ }
31+
32+ process.standardOutput = pipe
33+
34+ pipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
35+
36+ process.launch()
37+ process.waitUntilExit()
38+
39+ return out
40+ }
41+}
SourceDocsSwift/SourceKittenResponse.swiftnew file+123
@@ -0,0 +1,123 @@
1+//
2+// SourceKittenResponse.swift
3+// SourceDocsSwift
4+//
5+// Created by Ilya Kos on 8/22/18.
6+// Copyright © 2018 Ilya Kos. All rights reserved.
7+//
8+
9+import Foundation
10+
11+struct SourceKittenResponse: Decodable {
12+
13+ private enum CodingKeys: String, CodingKey {
14+ case objects = "key.substructure"
15+ }
16+
17+ var objects: [Object]
18+}
19+
20+struct Object: Decodable {
21+ let type: ObjectType
22+ let name: String
23+ /// This is a documented field
24+ let declaration: String
25+ let documentation: String?
26+ let methods: [Subobject]
27+ let fields: [Subobject]
28+
29+ private enum CodingKeys: String, CodingKey {
30+ case name = "key.name"
31+ case declaration = "key.parsed_declaration"
32+ case documenatation = "key.doc.comment"
33+ case type = "key.kind"
34+ case subobjects = "key.substructure"
35+ }
36+
37+ init(from decoder: Decoder) throws {
38+ let container = try decoder.container(keyedBy: CodingKeys.self)
39+ let typeString = try container.decodeIfPresent(String.self, forKey: .type)
40+ if typeString == "source.lang.swift.decl.struct" {
41+ type = .struct
42+ } else if typeString == "source.lang.swift.decl.class" {
43+ type = .class
44+ } else {
45+ type = .other
46+ name = ""
47+ declaration = ""
48+ documentation = nil
49+ methods = []
50+ fields = []
51+ return
52+ }
53+ name = try container.decode(String.self, forKey: .name).replacingOccurrences(of: "_", with: "\\_")
54+ declaration = try container.decode(String.self, forKey: .declaration).replacingOccurrences(of: "_", with: "\\_")
55+ documentation = try container.decodeIfPresent(String.self, forKey: .documenatation)?.replacingOccurrences(of: "_", with: "\\_")
56+ let subobjects = try container.decode([Subobject].self, forKey: .subobjects)
57+ methods = subobjects.filter {$0.type == .method}
58+ fields = subobjects.filter {$0.type == .field}
59+ }
60+}
61+
62+enum ObjectType {
63+ case `class`
64+ case `struct`
65+ case other
66+}
67+
68+struct Subobject: Decodable {
69+ let type: SubobjectType
70+ let name: String
71+ let declaration: String
72+ let documentation: String?
73+ let typeName: String
74+
75+ private enum CodingKeys: String, CodingKey {
76+ case name = "key.name"
77+ case declaration = "key.parsed_declaration"
78+ case documenatation = "key.doc.comment"
79+ case type = "key.kind"
80+ case typeName = "key.typename"
81+ }
82+
83+ private static let methodKeys: Set<String> = [
84+ "source.lang.swift.decl.function.method.static",
85+ "source.lang.swift.decl.function.method.instance"
86+ ]
87+
88+ private static let fieldKeys: Set<String> = [
89+ "source.lang.swift.decl.var.instance",
90+ "source.lang.swift.decl.var.static"
91+ ]
92+
93+ init(from decoder: Decoder) throws {
94+ let container = try decoder.container(keyedBy: CodingKeys.self)
95+ let stringType = try container.decode(String.self, forKey: .type)
96+ if Subobject.methodKeys.contains(stringType) {
97+ type = .method
98+ } else if Subobject.fieldKeys.contains(stringType) {
99+ type = .field
100+ } else {
101+ type = .other
102+ name = ""
103+ declaration = ""
104+ documentation = nil
105+ typeName = ""
106+ return
107+ }
108+ name = try container.decode(String.self, forKey: .name).replacingOccurrences(of: "_", with: "\\_")
109+ declaration = try container.decode(String.self, forKey: .declaration).replacingOccurrences(of: "_", with: "\\_")
110+ documentation = try container.decodeIfPresent(String.self, forKey: .documenatation)?.replacingOccurrences(of: "_", with: "\\_")
111+ typeName = try container.decode(String.self, forKey: .typeName).replacingOccurrences(of: "_", with: "\\_")
112+ }
113+}
114+
115+enum DecodingError: Error {
116+ case DecodingError
117+}
118+
119+enum SubobjectType {
120+ case method
121+ case field
122+ case other
123+}
SourceDocsSwift/TeXer.swiftnew file+64
@@ -0,0 +1,64 @@
1+//
2+// TeXer.swift
3+// SourceDocsSwift
4+//
5+// Created by Ilya Kos on 8/23/18.
6+// Copyright © 2018 Ilya Kos. All rights reserved.
7+//
8+
9+struct TeXer {
10+ static func convert(objects: [Object]) -> String {
11+ return """
12+ \\subsection{Описание классов и структур}
13+
14+ \\begin{longtable}{|p{0.55\\textwidth} | p{0.4\\textwidth}|}
15+ \\hline
16+ \\textbf{Класс или стркура} & \\textbf{Описание} \\\\ \\hline
17+ \( objects.map {"\\texttt{\($0.declaration)} & \($0.documentation ?? "{\\color{red} TODO}") \\\\ \\hline"} .joined(separator: "\n") )
18+ \\end{longtable}
19+
20+
21+ \\subsection{Описание полей классов и структур}
22+
23+ \(
24+ objects.compactMap {
25+ if $0.fields.count > 0 {
26+ return """
27+ \\subsubsection{\\texttt{\($0.declaration)}}
28+
29+ \\begin{longtable}{|p{0.55\\textwidth} | p{0.4\\textwidth}|}
30+ \\hline
31+ \\textbf{Поле} & \\textbf{Описание} \\\\ \\hline
32+ \( $0.fields.map {"\\texttt{\($0.declaration.prefix {$0 != "="})} & {\\color{red} TODO} \\\\ \\hline"} .joined(separator: "\n") )
33+ \\end{longtable}
34+
35+ """
36+ } else {
37+ return nil
38+ }
39+ } .joined(separator: "\n")
40+ )
41+
42+ \\subsection{Описание методов классов и структур}
43+
44+ \(
45+ objects.compactMap {
46+ if $0.methods.count > 0 {
47+ return """
48+ \\subsubsection{\\texttt{\($0.declaration)}}
49+
50+ \\begin{longtable}{|p{0.55\\textwidth} | p{0.4\\textwidth}|}
51+ \\hline
52+ \\textbf{Метод} & \\textbf{Описание} \\\\ \\hline
53+ \( $0.methods.map {"\\texttt{\($0.declaration)} & \($0.documentation ?? "{\\color{red} TODO}") \\\\ \\hline"} .joined(separator: "\n") )
54+ \\end{longtable}
55+
56+ """
57+ } else {
58+ return nil
59+ }
60+ } .joined(separator: "\n")
61+ )
62+ """
63+ }
64+}
SourceDocsSwift/XcodeBuildTargetResponse.swiftnew file+25
@@ -0,0 +1,25 @@
1+//
2+// XcodeBuildTargetResponse.swift
3+// SourceDocsSwift
4+//
5+// Created by Ilya Kos on 8/24/18.
6+// Copyright © 2018 Ilya Kos. All rights reserved.
7+//
8+
9+struct XcodeBuildTargetResponse: Decodable {
10+ private enum OuterKeys: CodingKey {
11+ case project
12+ }
13+
14+ private enum InnerKeys: CodingKey {
15+ case targets
16+ }
17+
18+ let targets: [String]
19+
20+ init(from decoder: Decoder) throws {
21+ let outerContainer = try decoder.container(keyedBy: OuterKeys.self)
22+ let innerContainer = try outerContainer.nestedContainer(keyedBy: InnerKeys.self, forKey: .project)
23+ targets = try innerContainer.decode([String].self, forKey: .targets)
24+ }
25+}
SourceDocsSwift/main.swift+82−1
@@ -8,5 +8,86 @@
8
8
9import Foundation
9import Foundation
10
10
11−print("Hello, World!")
11+//let directory = "/Users/ilya/Developer/Dotrix/Dotrix/"
12+//let output = "/Users/ilya/Desktop/foo.tex"
12
13
14+let args = Array(CommandLine.arguments.dropFirst())
15+let temp = FileManager.default.temporaryDirectory.appendingPathComponent("SourceDocsSwift/build\(Date().timeIntervalSince1970)")
16+
17+print(temp)
18+
19+var output = "docs.tex"
20+var directory: String?
21+
22+var i = 0
23+while i < args.count {
24+ switch args[i] {
25+ case "-o":
26+ if i + 1 < args.count {
27+ i += 1
28+ output = args[i]
29+ }
30+ case "-i":
31+ if i + 1 < args.count {
32+ i += 1
33+ directory = args[i]
34+ }
35+ default:
36+ break
37+ }
38+ i += 1
39+}
40+
41+let decoder = JSONDecoder()
42+
43+guard let xcbTagetData = Shell.launch(executable: "/usr/bin/xcodebuild", arguments: ["-list", "-json"], launchDirectory: directory) else {
44+ print("""
45+ Make sure xcodebuild is installed
46+
47+ xcode-select --install
48+ """)
49+ exit(1)
50+}
51+
52+guard let xcbResponse = try? decoder.decode(XcodeBuildTargetResponse.self, from: xcbTagetData) else {
53+ print("Unexpected response from xcodebuild")
54+ exit(1)
55+}
56+
57+let targets = xcbResponse.targets
58+
59+var files: [String: [Object]] = [:]
60+
61+for target in targets {
62+ try? FileManager.default.removeItem(at: temp)
63+ guard let skResponseData = Shell.launch(executable: "/usr/local/bin/sourcekitten", arguments: ["doc", "--", "-target", target, "CONFIGURATION_BUILD_DIR=\(temp)"], launchDirectory: directory) else {
64+ print("""
65+ Make sure sourcekitten is installed
66+
67+ brew install sourcekitten
68+ """)
69+ exit(1)
70+ }
71+
72+ guard let skResponse = try? decoder.decode([[String: SourceKittenResponse]].self, from: skResponseData) else {
73+ print("Unexpected response from sourcekitten")
74+ exit(1)
75+ }
76+
77+ skResponse.forEach {files.merge($0.mapValues {$0.objects.filter {$0.type != .other}}, uniquingKeysWith: {a, b in a})}
78+}
79+
80+
81+
82+
83+let tex = TeXer.convert(objects: files.flatMap {$0.value})
84+
85+try? tex.write(toFile: output, atomically: true, encoding: .utf8)
86+
87+print("""
88++-------+
89+| i l y |
90+| a k o |
91+| o o 0 |
92++-------+
93+""")