@@ -12,32 +12,92 @@ import SymbolKit
1212
1313@main
1414struct SnippetExtractCommand {
15- var snippetsDir : String
16- var outputDir : String
15+ enum OptionName : String {
16+ case moduleName = " --module-name "
17+ case outputFile = " --output "
18+ }
19+
20+ enum Argument {
21+ case moduleName( String )
22+ case outputFile( String )
23+ case inputFile( String )
24+ }
25+
26+ enum ArgumentError : Error , CustomStringConvertible {
27+ case missingOption( OptionName )
28+ case missingOptionValue( OptionName )
29+ case snippetNotContainedInSnippetsDirectory( URL )
30+
31+ var description : String {
32+ switch self {
33+ case . missingOption( let optionName) :
34+ return " Missing required option \( optionName. rawValue) "
35+ case . missingOptionValue( let optionName) :
36+ return " Missing required option value for \( optionName. rawValue) "
37+ case . snippetNotContainedInSnippetsDirectory( let snippetFileURL) :
38+ return " Snippet file ' \( snippetFileURL. path) ' is not contained in a directory called 'Snippets' at any level, so this tool is not able to compute the path components that would be used for linking to the snippet. It may exist in a subdirectory, but one of its parent directories must be named 'Snippets'. "
39+ }
40+ }
41+ }
42+
43+ var snippetFiles = [ String] ( )
44+ var outputFile : String
1745 var moduleName : String
1846
1947 static func printUsage( ) {
2048 let usage = """
21- USAGE: snippet-extract <snippet directory> <output directory> <module name>
49+ USAGE: snippet-extract --output <output file> --module-name <module name> <input files >
2250
2351 ARGUMENTS:
24- <snippet directory> - The directory containing Swift snippets
25- <output directory> - The diretory in which to place Symbol Graph JSON file(s) representing the snippets
26- <module name> - The module name to use for the Symbol Graph (typically should be the package name)
52+ <output file> (Required)
53+ The path of the output Symbol Graph JSON file representing the snippets for the a module or package
54+ <module name> (Required)
55+ The module name to use for the Symbol Graph (typically should be the package name)
56+ <input files>
57+ One or more absolute paths to snippet files to interpret as snippets
2758 """
2859 print ( usage)
2960 }
3061
62+ init ( arguments: [ String ] ) throws {
63+ var arguments = arguments
64+
65+ var parsedOutputFile : String ? = nil
66+ var parsedModuleName : String ? = nil
67+
68+ while let argument = try arguments. parseSnippetArgument ( ) {
69+ switch argument {
70+ case . inputFile( let inputFile) :
71+ snippetFiles. append ( inputFile)
72+ case . moduleName( let moduleName) :
73+ parsedModuleName = moduleName
74+ case . outputFile( let outputFile) :
75+ parsedOutputFile = outputFile
76+ }
77+ }
78+
79+ guard let parsedOutputFile else {
80+ throw ArgumentError . missingOption ( . outputFile)
81+ }
82+ self . outputFile = parsedOutputFile
83+
84+ guard let parsedModuleName else {
85+ throw ArgumentError . missingOption ( . moduleName)
86+ }
87+ self . moduleName = parsedModuleName
88+ }
89+
3190 func run( ) throws {
32- let snippets = try loadSnippets ( from: URL ( fileURLWithPath: snippetsDir) )
91+ let snippets = try snippetFiles. map {
92+ try Snippet ( parsing: URL ( fileURLWithPath: $0) )
93+ }
3394 guard snippets. count > 0 else { return }
34- let symbolGraphFilename = URL ( fileURLWithPath: outputDir)
35- . appendingPathComponent ( " \( moduleName) -snippets.symbols.json " )
95+ let symbolGraphFilename = URL ( fileURLWithPath: outputFile)
3696 try emitSymbolGraph ( for: snippets, to: symbolGraphFilename, moduleName: moduleName)
3797 }
3898
3999 func emitSymbolGraph( for snippets: [ Snippet ] , to emitFilename: URL , moduleName: String ) throws {
40- let snippetSymbols = snippets. map { SymbolGraph . Symbol ( $0, moduleName: moduleName, inDirectory : URL ( fileURLWithPath : snippetsDir ) . absoluteURL ) }
100+ let snippetSymbols = try snippets. map { try SymbolGraph . Symbol ( $0, moduleName: moduleName) }
41101 let metadata = SymbolGraph . Metadata ( formatVersion: . init( major: 0 , minor: 1 , patch: 0 ) , generator: " snippet-extract " )
42102 let module = SymbolGraph . Module ( name: moduleName, platform: . init( architecture: nil , vendor: nil , operatingSystem: nil , environment: nil ) , isVirtual: true )
43103 let symbolGraph = SymbolGraph ( metadata: metadata, module: module, symbols: snippetSymbols, relationships: [ ] )
@@ -72,28 +132,42 @@ struct SnippetExtractCommand {
72132 . filter { $0. isDirectory }
73133 }
74134
75- func loadSnippets( from snippetsDirectory: URL ) throws -> [ Snippet ] {
76- guard snippetsDirectory. isDirectory else {
77- return [ ]
78- }
79-
80- let snippetFiles = try files ( in: snippetsDirectory, withExtension: " swift " ) +
81- subdirectories( in: snippetsDirectory)
82- . flatMap { subdirectory -> [ URL ] in
83- try files ( in: subdirectory, withExtension: " swift " )
84- }
85-
86- return try snippetFiles. map { try Snippet ( parsing: $0) }
87- }
88-
89135 static func main( ) throws {
90- if CommandLine . arguments. count < 4 {
136+ if CommandLine . arguments. count == 1 || CommandLine . arguments . contains ( " -h " ) || CommandLine . arguments . contains ( " --help " ) {
91137 printUsage ( )
92138 exit ( 0 )
93139 }
94- let snippetExtract = SnippetExtractCommand ( snippetsDir: CommandLine . arguments [ 1 ] ,
95- outputDir: CommandLine . arguments [ 2 ] ,
96- moduleName: CommandLine . arguments [ 3 ] )
97- try snippetExtract. run ( )
140+ do {
141+ let snippetExtract = try SnippetExtractCommand ( arguments: Array ( CommandLine . arguments. dropFirst ( 1 ) ) )
142+ try snippetExtract. run ( )
143+ } catch let error as ArgumentError {
144+ printUsage ( )
145+ throw error
146+ }
147+ }
148+ }
149+
150+ extension Array where Element == String {
151+ mutating func parseSnippetArgument( ) throws -> SnippetExtractCommand . Argument ? {
152+ guard let thisArgument = first else {
153+ return nil
154+ }
155+ removeFirst ( )
156+ switch thisArgument {
157+ case " --module-name " :
158+ guard let nextArgument = first else {
159+ throw SnippetExtractCommand . ArgumentError. missingOptionValue ( . moduleName)
160+ }
161+ removeFirst ( )
162+ return . moduleName( nextArgument)
163+ case " --output " :
164+ guard let nextArgument = first else {
165+ throw SnippetExtractCommand . ArgumentError. missingOptionValue ( . outputFile)
166+ }
167+ removeFirst ( )
168+ return . outputFile( nextArgument)
169+ default :
170+ return . inputFile( thisArgument)
171+ }
98172 }
99173}
0 commit comments