@@ -3,39 +3,42 @@ import ArgumentParser
33import CodeOwners
44import SwiftParser
55
6+ private let defaultSettings : Settings = {
7+ let fm = FileManager . default
8+ if let root = fm. gitRoot, let settings = try ? readSettings ( atRoot: root) { return settings }
9+ return try ! readSettings ( atRoot: fm. pwd, evenIfMissing: true ) !
10+ } ( )
11+
612@main
713struct CodeOwnersTool : AsyncParsableCommand {
8-
14+
915 static let configuration : CommandConfiguration = . init(
1016 commandName: " swift-codeowners " ,
1117 abstract: " Generates code ownership information into Swift files "
1218 )
13-
19+
1420 @Argument ( help: " The Swift source files to process " )
1521 var sources : [ URL ]
16-
22+
1723 @Option ( name: [ . long, . customShort( " r " ) ] , help: " The root directory where the CODEOWNERS file patterns are based from. " )
18- var codeOwnersRoot : URL = URL ( filePath : settings . codeowners!. root!)
19-
24+ var codeOwnersRoot : URL = URL ( fileURLWithPath : defaultSettings . codeowners!. root!)
25+
2026 @Option ( name: . shortAndLong, help: " The CODEOWNERS file to use for determining ownership. " )
21- var codeOwnersFile : URL = URL ( filePath : settings . codeowners!. file!)
22-
27+ var codeOwnersFile : URL = URL ( fileURLWithPath : defaultSettings . codeowners!. file!)
28+
2329 @Option ( name: . shortAndLong, help: " The path to store the generated output CodeOwners attribution file " )
24- var outputFile : URL =
25- FileManager . default. pwd. appendingPathComponent ( " GeneratedSources/CodeOwners.swift " )
26-
30+ var outputFile : URL = FileManager . default. pwd. appendingPathComponent ( " GeneratedSources/CodeOwners.swift " )
31+
2732 @Option ( name: [ . customLong( " rename " ) ] , help: " Regex pattern to rename ownership names, in <regex>=<replacement> format) " )
28- var renames : [ RenameRule ] = settings . renames? . map ( asRenameRule) ?? [ ]
29-
33+ var renames : [ RenameRule ] = defaultSettings . renames? . map ( asRenameRule) ?? [ ]
34+
3035 @Flag ( name: . shortAndLong, inversion: . prefixedNo, help: " Enable verbose output for debugging purposes. " )
31- var verbose : Bool = settings . verbose ?? false
32-
36+ var verbose : Bool = defaultSettings . verbose ?? false
37+
3338 @Flag ( name: . shortAndLong, inversion: . prefixedNo, help: " Suppress non-error output. " )
34- var quiet : Bool = settings . quiet ?? false
35-
39+ var quiet : Bool = defaultSettings . quiet ?? false
40+
3641 func run( ) throws {
37- let fm = FileManager . default
38-
3942 if ( quiet && verbose) {
4043 print ( " Cannot use --quiet and --verbose flags together. " , to: & stdErr)
4144 return
@@ -44,22 +47,24 @@ struct CodeOwnersTool: AsyncParsableCommand {
4447 print ( " No source files provided. " , to: & stdErr)
4548 return
4649 }
50+
51+ let fm = FileManager . default
4752 if ( !fm. fileExists ( atPath: codeOwnersFile. path) ) {
4853 print ( " CODEOWNERS file not found at path: \( codeOwnersFile. path) . " , to: & stdErr)
4954 return
5055 }
51-
56+
5257 let codeOwners = try parseCodeOwners ( codeOwnersFile)
53-
58+
5459 var mappings : [ Substring : Set < String > ] = [ : ]
55-
60+
5661 try fm. walkFiles ( at: sources) { source in
5762 if source. pathExtension != " swift " { return }
5863 if verbose { print ( " Processing source file: \( source. path) " ) }
59-
64+
6065 guard let relativePath = source. relativePathTo ( codeOwnersRoot) else { return }
6166 guard let owners = codeOwners. codeOwner ( pattern: relativePath) ? . owners. map ( asLiteral) else { return }
62-
67+
6368 do {
6469 for typeName in try collectTypes ( from: source) {
6570 mappings [ typeName] = ( mappings [ typeName] ?? [ ] ) . union ( owners)
@@ -68,11 +73,11 @@ struct CodeOwnersTool: AsyncParsableCommand {
6873 print ( " warning: Failed to process source file ' \( relativePath) ': \( error) " , to: & stdErr)
6974 }
7075 }
71-
76+
7277 let content = generateContent ( mappings)
7378 try fm. createDirectory ( at: outputFile. deletingLastPathComponent ( ) , withIntermediateDirectories: true )
7479 try content. write ( to: outputFile, atomically: true , encoding: . utf8)
75-
80+
7681 if ( !quiet) {
7782 print ( " Generated CodeOwners attribution for \( mappings. count) types at: \( outputFile. path) " )
7883 }
@@ -105,16 +110,16 @@ struct CodeOwnersTool: AsyncParsableCommand {
105110
106111 private func asLiteral( _ owner: Owner ) -> String {
107112 switch owner {
108- case . user( let userId) :
109- switch userId {
110- case . userName( let name) : return " \( name) "
111- case . email( let email) : return " \( email) "
112- }
113- case . team( let teamId) :
114- return " \( teamId. organization) / \( teamId. name) "
113+ case . user( let userId) :
114+ switch userId {
115+ case . userName( let name) : return " \( name) "
116+ case . email( let email) : return " \( email) "
115117 }
118+ case . team( let teamId) :
119+ return " \( teamId. organization) / \( teamId. name) "
120+ }
116121 }
117-
122+
118123 private func collectTypes( from source: URL ) throws -> Set < Substring > {
119124 let swiftFileContent = try String ( contentsOf: source, encoding: . utf8)
120125 let swiftFile = Parser . parse ( source: swiftFileContent)
@@ -125,27 +130,26 @@ struct CodeOwnersTool: AsyncParsableCommand {
125130
126131 private func generateContent( _ mappings: [ Substring : Set < String > ] ) -> String {
127132 if mappings. isEmpty { return " " }
128-
133+
129134 var content = """
130- import CodeOwnersAPI
131-
132- internal class _CodeOwners : CodeOwnersMappingProvider {
133- static let codeOwners: [Substring: CodeOwners]? = [
134-
135- """
135+ import CodeOwnersAPI
136+
137+ internal class _CodeOwners : CodeOwnersMappingProvider {
138+ static let codeOwners: [Substring: CodeOwners]? = [
139+
140+ """
136141 for typeName in mappings. keys. sorted ( ) {
137142 let owners = mappings [ typeName] !. sorted ( ) . map { " \" \( $0) \" " } . joined ( separator: " , " )
138143
139144 content += " \" \( typeName) \" : [ \( owners) ], \n "
140145 }
141146 content += """
142- ]
143- }
144-
145- """
147+ ]
148+ }
149+
150+ """
146151 return content
147152 }
148-
149153}
150154
151155private func asRenameRule( regex: String , replace: String ) -> RenameRule {
0 commit comments