From d1637522060f00da8d2a36f7ba6d8479df9c88e1 Mon Sep 17 00:00:00 2001 From: Jota Uribe Date: Wed, 27 Oct 2021 15:53:51 -0500 Subject: [PATCH 1/6] Bump swift-tool-version to 5.5, package & project main structure clean up. --- Package.swift | 17 ++++------------- .../{ChromaLibrary => Chroma/App}/Chroma.swift | 1 - .../App}/OutputType.swift | 2 -- .../App}/Platform.swift | 4 ---- .../Extensions}/File.swift | 0 .../Extensions}/Folder.swift | 0 .../Extensions/String+Formatters.swift | 0 Sources/Chroma/main.swift | 1 - Tests/ChromaTests/FolderTests.swift | 2 +- Tests/ChromaTests/PlatformTests.swift | 2 +- 10 files changed, 6 insertions(+), 23 deletions(-) rename Sources/{ChromaLibrary => Chroma/App}/Chroma.swift (98%) rename Sources/{ChromaLibrary => Chroma/App}/OutputType.swift (98%) rename Sources/{ChromaLibrary => Chroma/App}/Platform.swift (97%) rename Sources/{ChromaLibrary => Chroma/Extensions}/File.swift (100%) rename Sources/{ChromaLibrary => Chroma/Extensions}/Folder.swift (100%) rename Sources/{ChromaLibrary => Chroma}/Extensions/String+Formatters.swift (100%) diff --git a/Package.swift b/Package.swift index dcee059..8b221f3 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.3 +// swift-tools-version:5.5 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -9,31 +9,22 @@ let package = Package( .macOS(.v10_15) ], products: [ - // Products define the executables and libraries produced by a package, and make them visible to other packages. - .executable(name: "Chroma", targets: ["Chroma"]), - .library(name: "ChromaLibrary", targets: ["ChromaLibrary"]) + .executable(name: "Chroma", targets: ["Chroma"]) ], dependencies: [ - // Dependencies declare other packages that this package depends on. - // .package(url: /* package url */, from: "1.0.0"), .package(url: "https://github.com/apple/swift-argument-parser", .exact("0.1.0")), .package(url: "https://github.com/JohnSundell/Files", from: "4.1.1") ], targets: [ - // Targets are the basic building blocks of a package. A target can define a module or a test suite. - // Targets can depend on other targets in this package, and on products in packages which this package depends on. - .target( + .executableTarget( name: "Chroma", - dependencies: ["ChromaLibrary"]), - .target( - name: "ChromaLibrary", dependencies: [ .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "Files", package: "Files") ]), .testTarget( name: "ChromaTests", - dependencies: ["ChromaLibrary"], + dependencies: ["Chroma"], resources: [.copy("Resources")]), ] ) diff --git a/Sources/ChromaLibrary/Chroma.swift b/Sources/Chroma/App/Chroma.swift similarity index 98% rename from Sources/ChromaLibrary/Chroma.swift rename to Sources/Chroma/App/Chroma.swift index 609defd..bc0f724 100644 --- a/Sources/ChromaLibrary/Chroma.swift +++ b/Sources/Chroma/App/Chroma.swift @@ -8,7 +8,6 @@ import ArgumentParser import Files -import Foundation public struct Chroma: ParsableCommand { @Option(name: .shortAndLong, default: "Colors", help: "Defines the name of the generated file.") diff --git a/Sources/ChromaLibrary/OutputType.swift b/Sources/Chroma/App/OutputType.swift similarity index 98% rename from Sources/ChromaLibrary/OutputType.swift rename to Sources/Chroma/App/OutputType.swift index 6402163..b925857 100644 --- a/Sources/ChromaLibrary/OutputType.swift +++ b/Sources/Chroma/App/OutputType.swift @@ -10,10 +10,8 @@ import Foundation import ArgumentParser enum OutputType: String, CaseIterable, ExpressibleByArgument { - case `extension` case `struct` - } extension OutputType { diff --git a/Sources/ChromaLibrary/Platform.swift b/Sources/Chroma/App/Platform.swift similarity index 97% rename from Sources/ChromaLibrary/Platform.swift rename to Sources/Chroma/App/Platform.swift index efc6dd1..ffe92ae 100644 --- a/Sources/ChromaLibrary/Platform.swift +++ b/Sources/Chroma/App/Platform.swift @@ -10,11 +10,9 @@ import Foundation import ArgumentParser enum Platform: String, ExpressibleByArgument { - case iOS case macOS case swiftUI - } extension Platform { @@ -37,7 +35,6 @@ extension Platform { func colorVariable(name: String) -> String { let formattedName = name.camelCased().removing(.punctuationCharacters.union(.symbols)) - print(formattedName) switch self { case .iOS, .macOS: return " static var \(formattedName): \(variableType) { return \(variableType)(named: \"\(name)\")! }" @@ -64,5 +61,4 @@ extension Platform { } """ } - } diff --git a/Sources/ChromaLibrary/File.swift b/Sources/Chroma/Extensions/File.swift similarity index 100% rename from Sources/ChromaLibrary/File.swift rename to Sources/Chroma/Extensions/File.swift diff --git a/Sources/ChromaLibrary/Folder.swift b/Sources/Chroma/Extensions/Folder.swift similarity index 100% rename from Sources/ChromaLibrary/Folder.swift rename to Sources/Chroma/Extensions/Folder.swift diff --git a/Sources/ChromaLibrary/Extensions/String+Formatters.swift b/Sources/Chroma/Extensions/String+Formatters.swift similarity index 100% rename from Sources/ChromaLibrary/Extensions/String+Formatters.swift rename to Sources/Chroma/Extensions/String+Formatters.swift diff --git a/Sources/Chroma/main.swift b/Sources/Chroma/main.swift index b235736..a3f67c2 100644 --- a/Sources/Chroma/main.swift +++ b/Sources/Chroma/main.swift @@ -6,7 +6,6 @@ // Copyright © 2020 Jota Uribe. All rights reserved. // -import ChromaLibrary import Foundation Chroma.main() diff --git a/Tests/ChromaTests/FolderTests.swift b/Tests/ChromaTests/FolderTests.swift index dd81fa8..b5d0d14 100644 --- a/Tests/ChromaTests/FolderTests.swift +++ b/Tests/ChromaTests/FolderTests.swift @@ -1,6 +1,6 @@ import XCTest import Files -@testable import ChromaLibrary +@testable import Chroma final class FolderTests: XCTestCase { func testColorDefinitionsForiOS() throws { diff --git a/Tests/ChromaTests/PlatformTests.swift b/Tests/ChromaTests/PlatformTests.swift index a3a3034..bf4191d 100644 --- a/Tests/ChromaTests/PlatformTests.swift +++ b/Tests/ChromaTests/PlatformTests.swift @@ -1,6 +1,6 @@ import XCTest import Files -@testable import ChromaLibrary +@testable import Chroma final class PlatformTests: XCTestCase { func testColorVariableForiOS() throws { From bc2cbb6d9e225b0235eedb3bf625e4adeec62aa0 Mon Sep 17 00:00:00 2001 From: Jota Uribe Date: Thu, 9 Jun 2022 09:40:52 -0500 Subject: [PATCH 2/6] Refactor chroma arguments to receive asset path and exported file path to make it work on a more standard and less risky way --- README.md | 12 ++--- Sources/Chroma/App/Chroma.swift | 72 ++++++++++++++++----------- Sources/Chroma/App/ChromaError.swift | 24 +++++++++ Sources/Chroma/Extensions/File.swift | 4 +- Tests/ChromaTests/FileTests.swift | 21 ++++++++ Tests/ChromaTests/FolderTests.swift | 7 +++ Tests/ChromaTests/PlatformTests.swift | 7 +++ 7 files changed, 110 insertions(+), 37 deletions(-) create mode 100644 Sources/Chroma/App/ChromaError.swift create mode 100644 Tests/ChromaTests/FileTests.swift diff --git a/README.md b/README.md index aef33d3..f497d85 100644 --- a/README.md +++ b/README.md @@ -7,19 +7,19 @@ A command line tool to generate swift colors definitions from .xcassets files. ``` $ Chroma --help -USAGE: chroma [--name ] [--output ] [--platform ] +USAGE: chroma --asset --path [--type ] [--platform ] OPTIONS: - -n, --name Defines the name of the generated file. (default: - Colors) - -o, --output Specifies generated file type. + -a, --asset The path of .xcasset file. + -p, --path The path of the generated .swift file. + -t, --type Specifies generated file type. Supported values: "extension","struct". (default: extension) - -p, --platform - Specifies the platform compatibility of the exported + --platform Specifies the platform compatibility of the exported file. iOS, macOS, swiftUI (default: iOS) -h, --help Show help information. + ``` ### Installation diff --git a/Sources/Chroma/App/Chroma.swift b/Sources/Chroma/App/Chroma.swift index bc0f724..e9c4bb5 100644 --- a/Sources/Chroma/App/Chroma.swift +++ b/Sources/Chroma/App/Chroma.swift @@ -8,51 +8,65 @@ import ArgumentParser import Files +import Foundation public struct Chroma: ParsableCommand { - @Option(name: .shortAndLong, default: "Colors", help: "Defines the name of the generated file.") - private var name: String + @Option(name: .shortAndLong, help: "The path of .xcasset file.") + private var asset: String + + @Option(name: .shortAndLong, help: "The path of the generated .swift file.") + private var path: String @Option(name: .shortAndLong, default: .extension, help: OutputType.help) - private var output: OutputType + private var type: OutputType - @Option(name: .shortAndLong, default: .iOS, help: "Specifies the platform compatibility of the exported file.\niOS, macOS, swiftUI") + @Option(name: .long, default: .iOS, help: "Specifies the platform compatibility of the exported file.\niOS, macOS, swiftUI") private var platform: Platform - private var header: String { - switch output { - case .extension: - return "\(output.rawValue) \(platform.variableType)" - case .struct: - return "\(output.rawValue) \(name)" - } - } - public init() {} public func run() throws { - generate() + try generate() } } extension Chroma { - private func generate() { - let folder = Folder.root - let file = folder.files.recursive.first(where: { $0.name == "\(name).swift" }) ?? File(named: name, at: folder) - let body = folder.colorDefinitions(for: platform).sorted().joined(separator: "\n\n") - let content = platform.fileContent(header: header, body: body) - do { - try file.write(content) - print( - """ - \(file.name) was generated successfully. - Can be found at \(file.path) - """ - ) - } catch { - print(error.localizedDescription) + private func generate() throws { + let outputFile = try outputFile() + let content = try getContentFromAssetsFile(outputFile: outputFile) + try outputFile.write(content) + print( + """ + \(outputFile.name) was generated successfully. + Can be found at \(outputFile.path) + """ + ) + } + + private func outputFile() throws -> File { + guard let pathURL = URL(string: path), !pathURL.hasDirectoryPath, pathURL.pathExtension == "swift" else { + throw ChromaError.invalidPath(path: path) + } + + + let folder = try Folder(path: pathURL.deletingLastPathComponent().path) + return File(named: pathURL.lastPathComponent, at: folder) + } + + private func getContentFromAssetsFile(outputFile: File) throws -> String { + let folder = try Folder(path: asset) + let body = folder.colorDefinitions(for: platform).sorted().joined(separator: "\n") + return platform.fileContent(header: header(file: outputFile), body: body) + } + + private func header(file: File) -> String { + switch type { + case .extension: + return "\(type.rawValue) \(platform.variableType)" + case .struct: + return "\(type.rawValue) \(file.nameExcludingExtension)" } } } diff --git a/Sources/Chroma/App/ChromaError.swift b/Sources/Chroma/App/ChromaError.swift new file mode 100644 index 0000000..8cb6272 --- /dev/null +++ b/Sources/Chroma/App/ChromaError.swift @@ -0,0 +1,24 @@ +// +// Error.swift +// +// +// Created by Jota Uribe on 9/06/22. +// + +import Foundation + +enum ChromaError: LocalizedError { + case fileCreationFailed(path: String) + case invalidPath(path: String) +} + +extension ChromaError { + var errorDescription: String? { + switch self { + case .fileCreationFailed(let path): + return "Could not create file at \(path)" + case .invalidPath(let path): + return "Invalid path: \(path)" + } + } +} diff --git a/Sources/Chroma/Extensions/File.swift b/Sources/Chroma/Extensions/File.swift index 0d11182..d17f34f 100644 --- a/Sources/Chroma/Extensions/File.swift +++ b/Sources/Chroma/Extensions/File.swift @@ -12,8 +12,8 @@ import Files extension File { init(named name: String, at folder: Folder) { - guard let file = try? folder.createFileIfNeeded(at: "\(name).swift") else { - fatalError("Error: Could not create file \(name).swift.") + guard let file = try? folder.createFileIfNeeded(at: name) else { + fatalError("Error: Could not create file \(name)") } self = file } diff --git a/Tests/ChromaTests/FileTests.swift b/Tests/ChromaTests/FileTests.swift new file mode 100644 index 0000000..b7d4907 --- /dev/null +++ b/Tests/ChromaTests/FileTests.swift @@ -0,0 +1,21 @@ +// +// FileTests.swift +// Chroma +// +// Created by Jota Uribe on 9/06/22. +// + +import XCTest +import Files +@testable import Chroma + +final class FileTests: XCTestCase { + + func testNamedAtInitWithOutExtension() throws { + let path = Bundle.module.bundlePath + let folder = try Folder(path: path) + let fileName = "File" + let file = File(named: fileName, at: folder) + XCTAssertEqual(file.path, "\(path)/\(fileName)") + } +} diff --git a/Tests/ChromaTests/FolderTests.swift b/Tests/ChromaTests/FolderTests.swift index b5d0d14..6c609b6 100644 --- a/Tests/ChromaTests/FolderTests.swift +++ b/Tests/ChromaTests/FolderTests.swift @@ -1,3 +1,10 @@ +// +// FileTests.swift +// Chroma +// +// Created by Jota Uribe on 9/06/22. +// + import XCTest import Files @testable import Chroma diff --git a/Tests/ChromaTests/PlatformTests.swift b/Tests/ChromaTests/PlatformTests.swift index bf4191d..e7d9785 100644 --- a/Tests/ChromaTests/PlatformTests.swift +++ b/Tests/ChromaTests/PlatformTests.swift @@ -1,3 +1,10 @@ +// +// FileTests.swift +// Chroma +// +// Created by Jota Uribe on 9/06/22. +// + import XCTest import Files @testable import Chroma From a01cd0a6244bf03abffcaf3d93571fc51d52080d Mon Sep 17 00:00:00 2001 From: Jota Uribe Date: Thu, 16 Jun 2022 18:00:41 -0500 Subject: [PATCH 3/6] Clean up on unit tests setup --- Sources/Chroma/App/ChromaError.swift | 4 ++-- Tests/ChromaTests/ChromaErrorTests.swift | 30 ++++++++++++++++++++++++ Tests/ChromaTests/FileTests.swift | 4 ++++ Tests/ChromaTests/XCTestManifests.swift | 2 ++ 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 Tests/ChromaTests/ChromaErrorTests.swift diff --git a/Sources/Chroma/App/ChromaError.swift b/Sources/Chroma/App/ChromaError.swift index 8cb6272..7917950 100644 --- a/Sources/Chroma/App/ChromaError.swift +++ b/Sources/Chroma/App/ChromaError.swift @@ -1,6 +1,6 @@ // -// Error.swift -// +// ChromaError.swift +// Chroma // // Created by Jota Uribe on 9/06/22. // diff --git a/Tests/ChromaTests/ChromaErrorTests.swift b/Tests/ChromaTests/ChromaErrorTests.swift new file mode 100644 index 0000000..65d4716 --- /dev/null +++ b/Tests/ChromaTests/ChromaErrorTests.swift @@ -0,0 +1,30 @@ +// +// ChromaErrorTests.swift +// Chroma +// +// Created by Jota Uribe on 9/06/22. +// + +import XCTest +@testable import Chroma + +final class ChromaErrorTests: XCTestCase { + let mockPath = "/path/file.ext" + + func testFileCreationFailedDescription() throws { + let error = ChromaError.fileCreationFailed(path: mockPath) + let expectedDesc = "Could not create file at /path/file.ext" + XCTAssertEqual(error.localizedDescription, expectedDesc) + } + + func testInvalidPathDescription() throws { + let error = ChromaError.invalidPath(path: mockPath) + let expectedDesc = "Invalid path: /path/file.ext" + XCTAssertEqual(error.localizedDescription, expectedDesc) + } + + static var allTests = [ + ("testFileCreationFailedDescription", testFileCreationFailedDescription), + ("testInvalidPathDescription", testInvalidPathDescription) + ] +} diff --git a/Tests/ChromaTests/FileTests.swift b/Tests/ChromaTests/FileTests.swift index b7d4907..7486ae7 100644 --- a/Tests/ChromaTests/FileTests.swift +++ b/Tests/ChromaTests/FileTests.swift @@ -18,4 +18,8 @@ final class FileTests: XCTestCase { let file = File(named: fileName, at: folder) XCTAssertEqual(file.path, "\(path)/\(fileName)") } + + static var allTests = [ + ("testNamedAtInitWithOutExtension", testNamedAtInitWithOutExtension) + ] } diff --git a/Tests/ChromaTests/XCTestManifests.swift b/Tests/ChromaTests/XCTestManifests.swift index e99ae44..70bab30 100644 --- a/Tests/ChromaTests/XCTestManifests.swift +++ b/Tests/ChromaTests/XCTestManifests.swift @@ -3,6 +3,8 @@ import XCTest #if !canImport(ObjectiveC) public func allTests() -> [XCTestCaseEntry] { return [ + testCase(ChromaErrorTests.allTests), + testCase(FileTests.allTests), testCase(FolderTests.allTests), testCase(PlatformTests.allTests) ] From 9ab73b49b8129d7b5de47f7fd7e09bd636be954e Mon Sep 17 00:00:00 2001 From: Jota Uribe Date: Thu, 16 Jun 2022 18:05:19 -0500 Subject: [PATCH 4/6] Update swift-argument-parser version to 1.1.2 --- Package.swift | 2 +- Sources/Chroma/App/Chroma.swift | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Package.swift b/Package.swift index 8b221f3..d6712ef 100644 --- a/Package.swift +++ b/Package.swift @@ -12,7 +12,7 @@ let package = Package( .executable(name: "Chroma", targets: ["Chroma"]) ], dependencies: [ - .package(url: "https://github.com/apple/swift-argument-parser", .exact("0.1.0")), + .package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0"), .package(url: "https://github.com/JohnSundell/Files", from: "4.1.1") ], targets: [ diff --git a/Sources/Chroma/App/Chroma.swift b/Sources/Chroma/App/Chroma.swift index e9c4bb5..143f288 100644 --- a/Sources/Chroma/App/Chroma.swift +++ b/Sources/Chroma/App/Chroma.swift @@ -17,11 +17,11 @@ public struct Chroma: ParsableCommand { @Option(name: .shortAndLong, help: "The path of the generated .swift file.") private var path: String - @Option(name: .shortAndLong, default: .extension, help: OutputType.help) - private var type: OutputType + @Option(name: .shortAndLong, help: OutputType.help) + private var type: OutputType = .extension - @Option(name: .long, default: .iOS, help: "Specifies the platform compatibility of the exported file.\niOS, macOS, swiftUI") - private var platform: Platform + @Option(name: .long, help: "Specifies the platform compatibility of the exported file.\niOS, macOS, swiftUI") + private var platform: Platform = .iOS public init() {} From b86ae4c8451fa3f7fa17994cdcacadecacfb101b Mon Sep 17 00:00:00 2001 From: Jota Uribe Date: Thu, 16 Jun 2022 18:22:55 -0500 Subject: [PATCH 5/6] Update folder file creation method to throw an error instead of causing a fatal error --- Sources/Chroma/App/Chroma.swift | 2 +- Sources/Chroma/App/ChromaError.swift | 3 +++ Sources/Chroma/Extensions/File.swift | 4 ++-- Tests/ChromaTests/ChromaErrorTests.swift | 8 ++++++++ Tests/ChromaTests/FileTests.swift | 2 +- 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Sources/Chroma/App/Chroma.swift b/Sources/Chroma/App/Chroma.swift index 143f288..59bba28 100644 --- a/Sources/Chroma/App/Chroma.swift +++ b/Sources/Chroma/App/Chroma.swift @@ -52,7 +52,7 @@ extension Chroma { let folder = try Folder(path: pathURL.deletingLastPathComponent().path) - return File(named: pathURL.lastPathComponent, at: folder) + return try File(named: pathURL.lastPathComponent, at: folder) } private func getContentFromAssetsFile(outputFile: File) throws -> String { diff --git a/Sources/Chroma/App/ChromaError.swift b/Sources/Chroma/App/ChromaError.swift index 7917950..015b9f6 100644 --- a/Sources/Chroma/App/ChromaError.swift +++ b/Sources/Chroma/App/ChromaError.swift @@ -9,6 +9,7 @@ import Foundation enum ChromaError: LocalizedError { case fileCreationFailed(path: String) + case fileCreationFailedAtFolder(path: String, fileName: String) case invalidPath(path: String) } @@ -17,6 +18,8 @@ extension ChromaError { switch self { case .fileCreationFailed(let path): return "Could not create file at \(path)" + case .fileCreationFailedAtFolder(let path, let fileName): + return "Could not create file '\(fileName)' at \(path)" case .invalidPath(let path): return "Invalid path: \(path)" } diff --git a/Sources/Chroma/Extensions/File.swift b/Sources/Chroma/Extensions/File.swift index d17f34f..c69c131 100644 --- a/Sources/Chroma/Extensions/File.swift +++ b/Sources/Chroma/Extensions/File.swift @@ -11,9 +11,9 @@ import Files extension File { - init(named name: String, at folder: Folder) { + init(named name: String, at folder: Folder) throws { guard let file = try? folder.createFileIfNeeded(at: name) else { - fatalError("Error: Could not create file \(name)") + throw ChromaError.fileCreationFailedAtFolder(path: folder.path, fileName: name) } self = file } diff --git a/Tests/ChromaTests/ChromaErrorTests.swift b/Tests/ChromaTests/ChromaErrorTests.swift index 65d4716..b5a365a 100644 --- a/Tests/ChromaTests/ChromaErrorTests.swift +++ b/Tests/ChromaTests/ChromaErrorTests.swift @@ -17,14 +17,22 @@ final class ChromaErrorTests: XCTestCase { XCTAssertEqual(error.localizedDescription, expectedDesc) } + func testFileCreationFailedAtFolder() throws { + let error = ChromaError.fileCreationFailedAtFolder(path: "/path", fileName: "file.ext") + let expectedDesc = "Could not create file 'file.ext' at /path" + XCTAssertEqual(error.localizedDescription, expectedDesc) + } + func testInvalidPathDescription() throws { let error = ChromaError.invalidPath(path: mockPath) let expectedDesc = "Invalid path: /path/file.ext" XCTAssertEqual(error.localizedDescription, expectedDesc) } + static var allTests = [ ("testFileCreationFailedDescription", testFileCreationFailedDescription), + ("testFileCreationFailedAtFolder", testFileCreationFailedAtFolder), ("testInvalidPathDescription", testInvalidPathDescription) ] } diff --git a/Tests/ChromaTests/FileTests.swift b/Tests/ChromaTests/FileTests.swift index 7486ae7..089463b 100644 --- a/Tests/ChromaTests/FileTests.swift +++ b/Tests/ChromaTests/FileTests.swift @@ -15,7 +15,7 @@ final class FileTests: XCTestCase { let path = Bundle.module.bundlePath let folder = try Folder(path: path) let fileName = "File" - let file = File(named: fileName, at: folder) + let file = try File(named: fileName, at: folder) XCTAssertEqual(file.path, "\(path)/\(fileName)") } From 5c2d9d63686368ffac82c81b466d2f375967cc12 Mon Sep 17 00:00:00 2001 From: Jota Uribe Date: Thu, 16 Jun 2022 18:26:28 -0500 Subject: [PATCH 6/6] Update Chroma description on Readme file --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f497d85..3e05852 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Chroma -A command line tool to generate swift colors definitions from .xcassets files. +A command line tool to auto generate .swift extensions or structs files from .xcassets on your iOS, macOS & SwiftUI projects. ### Usage