@@ -26,10 +26,19 @@ import ArgumentParser
2626import SwiftSource
2727
2828
29- extension String : LocalizedError {
30- public var errorDescription : String ? { self }
29+ enum Errors : String {
30+ case cantOpenFile = " Can't open file. "
31+ case filesNotFound = " Swift files not found. "
32+ case declarationsNotFound = " Swift declarations not found. "
33+ case pathNotFound = " Path not found. "
34+ case notSwiftFile = " Not swift file. "
3135}
3236
37+ extension Errors : LocalizedError {
38+ public var errorDescription : String ? { self . rawValue }
39+ }
40+
41+
3342enum AccessLevel : String , ExpressibleByArgument {
3443 case open, `public`, `internal`, `fileprivate`, `private`
3544
@@ -75,24 +84,39 @@ struct SwiftDocCoverage: ParsableCommand {
7584 @Option ( name: . shortAndLong, help: " The file path for generated report. " )
7685 var output : String ?
7786
87+ private enum CodingKeys : String , CodingKey {
88+ case inputs, skipsHiddenFiles, ignoreFilenameRegex, minimumAccessLevel, report, output
89+ }
90+
7891 var sources : [ SwiftSource ] = [ ]
92+ var out : Output ? = nil
7993
8094 mutating func run( ) throws {
81- let out : Output = output != nil
82- ? try FileOutput ( path: output!)
83- : TerminalOutput ( )
95+ // Output
96+ if out == nil {
97+ if let output = output {
98+ if let file = try FileOutput ( path: output) {
99+ out = file
100+ }
101+ else {
102+ throw Errors . cantOpenFile
103+ }
104+ }
105+ else {
106+ out = TerminalOutput ( )
107+ }
108+ }
84109
85110 let urls = try inputs. flatMap {
86111 try Self . files ( path: $0, ext: " .swift " , skipsHiddenFiles: skipsHiddenFiles, ignoreFilenameRegex: ignoreFilenameRegex)
87112 }
88113
89114 guard urls. count > 0 else {
90- throw " Swift files not found. "
115+ throw Errors . filesNotFound
91116 }
92117
93118 let totalTime = Date ( )
94119 var i = 0
95- let minAccessLevel = minimumAccessLevel. accessLevel. rawValue
96120
97121 // Sources
98122 sources = try urls. map { url in
@@ -101,7 +125,7 @@ struct SwiftDocCoverage: ParsableCommand {
101125 let source = try SwiftSource ( fileURL: url)
102126
103127 if report != . json {
104- let declarations = source. declarations. filter { $0 . accessLevel. rawValue <= minAccessLevel }
128+ let declarations = source. declarations ( level : minimumAccessLevel . accessLevel)
105129 if declarations. count > 0 {
106130 i += 1
107131 let filePath = url. absoluteString
@@ -118,9 +142,9 @@ struct SwiftDocCoverage: ParsableCommand {
118142 }
119143
120144 if report == . coverage {
121- let declarations = sources. flatMap { $0. declarations. filter { $0 . accessLevel. rawValue <= minAccessLevel } }
145+ let declarations = sources. flatMap { $0. declarations ( level : minimumAccessLevel . accessLevel) }
122146 guard declarations. count > 0 else {
123- throw " Swift declarations not found. "
147+ throw Errors . declarationsNotFound
124148 }
125149
126150 let undocumented = declarations. filter { $0. isDocumented == false }
@@ -129,7 +153,7 @@ struct SwiftDocCoverage: ParsableCommand {
129153 let documentedCount = totalCount - undocumented. count
130154 let coverage = documentedCount * 100 / totalCount
131155
132- out. write ( " \n Total: \( coverage) % [ \( documentedCount) / \( totalCount) ] ( \( Self . string ( from: - totalTime. timeIntervalSinceNow) ) ) " )
156+ out? . write ( " \n Total: \( coverage) % [ \( documentedCount) / \( totalCount) ] ( \( Self . string ( from: - totalTime. timeIntervalSinceNow) ) ) " )
133157 }
134158 else if report == . json {
135159 print ( sources)
@@ -143,16 +167,17 @@ struct SwiftDocCoverage: ParsableCommand {
143167 }
144168 }
145169
146- static func run( _ arguments : [ String ] ? = nil ) throws -> Self {
170+ static func run( output : Output ? = nil , _ arguments : String ... ) throws -> Self {
147171 var cmd = try Self . parse ( arguments)
172+ cmd. out = output
148173 try cmd. run ( )
149174 return cmd
150175 }
151176
152177 static func files( path: String , ext: String , skipsHiddenFiles: Bool , ignoreFilenameRegex: String ) throws -> [ URL ] {
153178 var isDirectory : ObjCBool = false
154179 guard FileManager . default. fileExists ( atPath: path, isDirectory: & isDirectory) else {
155- throw " Path not found. "
180+ throw Errors . pathNotFound
156181 }
157182
158183 if isDirectory. boolValue {
@@ -178,7 +203,7 @@ struct SwiftDocCoverage: ParsableCommand {
178203 // Skip by regex
179204 if let regex = regex {
180205 let fileName = fileURL. lastPathComponent
181- let range = NSRange ( location : 0 , length : fileName. utf16 . count )
206+ let range = NSRange ( fileName . startIndex ... , in : fileName)
182207 if regex. firstMatch ( in: fileName, range: range) != nil {
183208 continue
184209 }
@@ -191,7 +216,7 @@ struct SwiftDocCoverage: ParsableCommand {
191216 }
192217
193218 guard path. hasSuffix ( ext) else {
194- throw " Not swift file. "
219+ throw Errors . notSwiftFile
195220 }
196221 let url = URL ( fileURLWithPath: path)
197222 return [ url]
@@ -222,7 +247,7 @@ struct SwiftDocCoverage: ParsableCommand {
222247 return time
223248 }
224249
225- static func coverage( i: Int , time: Date , filePath: String , declarations: [ SwiftDeclaration ] , out: Output ) {
250+ static func coverage( i: Int , time: Date , filePath: String , declarations: [ SwiftDeclaration ] , out: Output ? ) {
226251 assert ( declarations. count > 0 )
227252
228253 let undocumented = declarations. filter { $0. isDocumented == false }
@@ -231,26 +256,26 @@ struct SwiftDocCoverage: ParsableCommand {
231256 let documentedCount = totalCount - undocumented. count
232257 let coverage = documentedCount * 100 / totalCount
233258
234- out. write ( " \( i) ) \( filePath) : \( coverage) % [ \( documentedCount) / \( totalCount) ] ( \( string ( from: - time. timeIntervalSinceNow) ) ) " )
259+ out? . write ( " \( i) ) \( filePath) : \( coverage) % [ \( documentedCount) / \( totalCount) ] ( \( string ( from: - time. timeIntervalSinceNow) ) ) " )
235260
236261 if undocumented. count > 0 {
237262 let fileName = NSString ( string: filePath) . lastPathComponent
238263
239- out. write ( " Undocumented: " )
264+ out? . write ( " Undocumented: " )
240265 undocumented. forEach {
241- out. write ( " < \( fileName) : \( $0. line) : \( $0. column) > \( $0. name) " )
266+ out? . write ( " < \( fileName) : \( $0. line) : \( $0. column) > \( $0. name) " )
242267 }
243- out. write ( " \n " , terminator: " " )
268+ out? . write ( " \n " , terminator: " " )
244269 }
245270 }
246271
247- static func warnings( filePath: String , declarations: [ SwiftDeclaration ] , out: Output ) {
272+ static func warnings( filePath: String , declarations: [ SwiftDeclaration ] , out: Output ? ) {
248273 assert ( declarations. count > 0 )
249274
250275 declarations
251276 . filter { $0. isDocumented == false }
252277 . forEach {
253- out. write ( " \( filePath) : \( $0. line) : \( $0. column) : warning: No documentation for ' \( $0. name) '. " )
278+ out? . write ( " \( filePath) : \( $0. line) : \( $0. column) : warning: No documentation for ' \( $0. name) '. " )
254279 }
255280 }
256281}
0 commit comments