@@ -3,14 +3,13 @@ import Foundation
3
3
4
4
@main
5
5
struct AssetGenCLI : ParsableCommand {
6
- @Option ( help: " Directory containing all files it should generate static " )
7
- var directory : String
6
+ @Option ( name : [ . customLong ( " input " ) ] , help: " Directory containing all files it should generate static " )
7
+ var inputs : [ String ]
8
8
9
9
@Option ( help: " The path where the generated output will be created " )
10
10
var output : String
11
11
12
12
func run( ) throws {
13
- let dir = URL ( filePath: directory, directoryHint: . isDirectory)
14
13
let outFile = URL ( filePath: output, directoryHint: . notDirectory)
15
14
16
15
let fileName = outFile. deletingPathExtension ( ) . lastPathComponent
@@ -20,99 +19,155 @@ struct AssetGenCLI: ParsableCommand {
20
19
throw Error . swiftExtensionNotInOutfile
21
20
}
22
21
23
- let items = Self . recursive ( dir )
22
+ let items = inputs . map { Self . recursive ( URL ( filePath : $0 , directoryHint : . checkFileSystem ) ) }
24
23
25
24
try """
25
+ import Foundation
26
26
public struct \( fileName. pascalCase ( ) ) : Swift.Sendable {
27
- public let basePath: String
28
- public init(_ basePath: String = " / " ) {
29
- self.basePath = basePath
27
+ public let baseURL: URL
28
+ public init() {
29
+ self.baseURL = Bundle.module.bundleURL
30
+ }
31
+ public init(_ baseURL: URL) {
32
+ self.baseURL = baseURL
33
+ }
34
+ \( items. map { $0. code ( isFirstLevel: true ) } . joined ( separator: " \n " ) , indent: 2 )
35
+ public protocol File {
36
+ var name: String { get }
37
+ var ext: String? { get }
38
+ var url: URL { get }
30
39
}
31
- \( items. map { $0. code ( ) } . joined ( separator: " \n " ) )
32
40
public struct AnyFile: Swift.Sendable {
33
41
public let name: String
34
- public let ext: String
35
- public let path: String
42
+ public let ext: String?
43
+ public let url: URL
36
44
}
37
45
public struct ImageFile: Swift.Sendable {
38
46
public let name: String
39
- public let ext: String
40
- public let path: String
41
- public let mimeType = " "
47
+ public let ext: String?
48
+ public let url: URL
49
+ public let width: Int?
50
+ public let height: Int?
42
51
}
43
52
public struct VideoFile: Swift.Sendable {
44
53
public let name: String
45
- public let ext: String
46
- public let path: String
47
- public let width: Int = 0
48
- public let height: Int = 0
49
- public let format: String = " "
50
- public let mimeType = " "
54
+ public let ext: String?
55
+ public let url: URL
56
+ public let width: Int?
57
+ public let height: Int?
58
+ public let mime: String
51
59
}
52
60
}
53
61
"""
54
62
. write ( to: outFile, atomically: true , encoding: . utf8)
55
63
56
- print ( " Successfully parsed ' \( dir ) ' and wrote to ' \( outFile ) ' " )
64
+ print ( " Successfully parsed ' \( inputs ) ' directory and generated to ' \( output ) ' " )
57
65
}
58
66
59
67
private enum Error : Swift . Error {
60
68
case swiftExtensionNotInOutfile
61
69
}
62
70
63
- private static func recursive( _ dir: URL ) -> [ FileOrDir ] {
64
- guard let enumerator = try ? FileManager . default. contentsOfDirectory (
65
- at: dir,
66
- includingPropertiesForKeys: [ . nameKey, . isDirectoryKey] ,
67
- options: [ . skipsHiddenFiles]
68
- ) else {
69
- return [ ]
70
- }
71
-
72
- return enumerator. compactMap { url in
73
- let resourceValues = try ? url. resourceValues ( forKeys: [ . isDirectoryKey] )
74
- let isDirectory = resourceValues? . isDirectory ?? false
75
-
76
- if isDirectory {
77
- return . dir(
78
- canonical: url. lastPathComponent,
79
- recursive ( url)
80
- )
81
- } else {
82
- return . file(
83
- canonical: url. deletingPathExtension ( ) . lastPathComponent,
84
- ext: url. pathExtension
85
- )
71
+ private static func recursive( _ url: URL ) -> FileOrDir {
72
+ var isDirectory = false
73
+ _ = FileManager . default. fileExists ( atPath: url. path ( ) , isDirectory: & isDirectory)
74
+
75
+ if isDirectory {
76
+ guard let enumerator = try ? FileManager . default. contentsOfDirectory (
77
+ at: url,
78
+ includingPropertiesForKeys: [ . isDirectoryKey, . fileSizeKey] ,
79
+ options: [ . skipsHiddenFiles]
80
+ ) else {
81
+ return . dir( canonical: url. lastPathComponent, [ ] )
86
82
}
83
+
84
+ return . dir(
85
+ canonical: url. lastPathComponent,
86
+ enumerator. compactMap ( Self . recursive)
87
+ )
88
+ } else {
89
+ return . file(
90
+ canonical: url. deletingPathExtension ( ) . lastPathComponent,
91
+ ext: url. pathExtension. isEmpty ? nil : url. pathExtension,
92
+ type: . from( ext: url. pathExtension)
93
+ )
87
94
}
88
95
}
89
96
90
97
private enum FileOrDir {
91
98
case dir( canonical: String , [ Self ] )
92
- case file( canonical: String , ext: String )
99
+ case file( canonical: String , ext: String ? , type : FileType )
93
100
94
- func code( _ indent : Int = 2 , path : [ String ] = [ ] ) -> String {
101
+ func code( path : [ String ] = [ ] , isFirstLevel : Bool = false ) -> String {
95
102
switch self {
96
103
case let . dir( canonical, items) :
97
104
"""
98
- \( String ( repeating: " " , count: indent) ) public var ` \( canonical. camelCase ( ) ) `: \( canonical. pascalCase ( ) ) {
99
- \( String ( repeating: " " , count: indent) ) \( canonical. pascalCase ( ) ) (basePath: self.basePath)
100
- \( String ( repeating: " " , count: indent) ) }
101
- \( String ( repeating: " " , count: indent) ) public struct \( canonical. pascalCase ( ) ) : Swift.Sendable {
102
- \( String ( repeating: " " , count: indent) ) fileprivate let basePath: String
103
- \( items. map { $0. code ( indent + 2 , path: path + [ canonical] ) } . joined ( separator: " \n " ) )
104
- \( String ( repeating: " " , count: indent) ) }
105
+ public var ` \( canonical. camelCase ( ) ) `: \( canonical. pascalCase ( ) ) {
106
+ \( canonical. pascalCase ( ) ) (baseURL: \( isFirstLevel ? " URL(filePath: \" \( canonical) \" , directoryHint: .isDirectory, relativeTo: self.baseURL) " : " self.baseURL.appending(path: \" \( canonical) \" , directoryHint: .isDirectory) " ) )
107
+ }
108
+ public struct \( canonical. pascalCase ( ) ) : Swift.Sendable {
109
+ public let baseURL: URL
110
+ \( items. map { $0. code ( path: path + [ canonical] ) } . joined ( separator: " \n " ) , indent: 2 )
111
+ }
112
+ """
113
+ case let . file( canonical, ext, type) :
114
+ switch type {
115
+ case . unknown:
116
+ """
117
+ public var ` \( canonical. camelCase ( ) ) `: AnyFile {
118
+ .init(
119
+ name: " \( canonical) " ,
120
+ ext: \( ext. flatMap { " \" \( $0) \" " } ?? " nil " ) ,
121
+ url: self.baseURL.appending(path: " \( canonical) " , directoryHint: .notDirectory) \( ext. flatMap { " .appendingPathExtension( \" \( $0) \" ) " } ?? " " )
122
+ )
123
+ }
124
+ """
125
+ case . image( let width, let height) :
126
+ """
127
+ public var ` \( canonical. camelCase ( ) ) `: ImageFile {
128
+ .init(
129
+ name: " \( canonical) " ,
130
+ ext: \( ext. flatMap { " \" \( $0) \" " } ?? " nil " ) ,
131
+ url: self.baseURL.appending(path: " \( canonical) " , directoryHint: .notDirectory) \( ext. flatMap { " .appendingPathExtension( \" \( $0) \" ) " } ?? " " ) ,
132
+ width: \( width. flatMap ( String . init) ?? " nil " ) ,
133
+ height: \( height. flatMap ( String . init) ?? " nil " )
134
+ )
135
+ }
105
136
"""
106
- case let . file ( canonical , ext ) :
137
+ case let . video ( width , height , mime ) :
107
138
"""
108
- \( String ( repeating: " " , count: indent) ) public var ` \( canonical. camelCase ( ) ) `: AnyFile {
109
- \( String ( repeating: " " , count: indent) ) AnyFile(
110
- \( String ( repeating: " " , count: indent) ) name: " \( canonical) " ,
111
- \( String ( repeating: " " , count: indent) ) ext: " \( ext) " ,
112
- \( String ( repeating: " " , count: indent) ) path: " \\ (self.basePath)/ \( ( path + [ " \( canonical) . \( ext) " ] ) . joined ( separator: " / " ) ) "
113
- \( String ( repeating: " " , count: indent) ) )
114
- \( String ( repeating: " " , count: indent) ) }
139
+ public var ` \( canonical. camelCase ( ) ) `: VideoFile {
140
+ .init(
141
+ name: " \( canonical) " ,
142
+ ext: \( ext. flatMap { " \" \( $0) \" " } ?? " nil " ) ,
143
+ url: self.baseURL.appending(path: " \( canonical) " , directoryHint: .notDirectory) \( ext. flatMap { " .appendingPathExtension( \" \( $0) \" ) " } ?? " " ) ,
144
+ width: \( width. flatMap ( String . init) ?? " nil " ) ,
145
+ height: \( height. flatMap ( String . init) ?? " nil " ) ,
146
+ mime: " \( mime) "
147
+ )
148
+ }
115
149
"""
150
+ }
151
+ }
152
+ }
153
+
154
+ enum FileType {
155
+ case image( width: Int ? , height: Int ? )
156
+ case video( width: Int ? , height: Int ? , mime: String )
157
+ case unknown
158
+
159
+ static func from( ext: String ) -> Self {
160
+ switch ext. trimmingCharacters ( in: . whitespacesAndNewlines) {
161
+ case " gif " : . image( width: nil , height: nil )
162
+ case " jpeg " , " jpg " : . image( width: nil , height: nil )
163
+ case " svg " : . image( width: nil , height: nil )
164
+ case " webp " : . image( width: nil , height: nil )
165
+ case " png " : . image( width: nil , height: nil )
166
+ case " mp4 " : . video( width: nil , height: nil , mime: " video/mp4 " )
167
+ case " mov " : . video( width: nil , height: nil , mime: " video/quicktime " )
168
+ case " webm " : . video( width: nil , height: nil , mime: " video/webm " )
169
+ default : . unknown
170
+ }
116
171
}
117
172
}
118
173
}
@@ -150,4 +205,10 @@ private extension String {
150
205
}
151
206
. joined ( )
152
207
}
208
+ }
209
+
210
+ private extension String . StringInterpolation {
211
+ mutating func appendInterpolation< S: StringProtocol > ( _ value: S , indent: Int ) {
212
+ appendInterpolation ( value. components ( separatedBy: " \n " ) . joined ( separator: " \n \( String ( repeating: " " , count: indent) ) " ) )
213
+ }
153
214
}
0 commit comments