Skip to content

Commit

Permalink
Release 0.6.0 (#17)
Browse files Browse the repository at this point in the history
* Refactor Platform type to Framework which is more aligned with the concept of what we are representing, default Framework is now SwiftUI, Update README file

* Move all file generation logic to new FileGenerator class, Add unit test for header method, header method update to validate struct file name is Capitalized.

* Update help info for some parameters
  • Loading branch information
jjotaum committed Jan 29, 2024
1 parent ac14c8a commit d696be7
Show file tree
Hide file tree
Showing 10 changed files with 362 additions and 219 deletions.
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
# Chroma

A command line tool to auto generate .swift extensions or structs files from .xcassets on your iOS, macOS & SwiftUI projects.
A command line tool to generate .swift extensions or structs files from .xcassets on your UIKit, AppKIt or SwiftUI projects.

### Usage

```
$ Chroma --help
USAGE: chroma --asset <asset> --path <path> [--type <type>] [--platform <platform>]
USAGE: chroma --asset <asset> --path <path> [--type <type>] [--framework <framework>]
OPTIONS:
-a, --asset <asset> The path of .xcasset file.
-p, --path <path> The path of the generated .swift file.
-t, --type <type> Specifies generated file type.
Supported values: "extension","struct". (default:
-a, --asset <asset> The path of .xcasset file.
-p, --path <path> The path of the generated .swift file.
-t, --type <type> The output type of generated .swift file.
Supported values: extension, struct. (default:
extension)
--platform <platform> Specifies the platform compatibility of the exported
file.
iOS, macOS, swiftUI (default: iOS)
--framework <framework> The framework compatibility of generated .swift file.
Supported values: AppKit, SwiftUI, UIKit. (default:
SwiftUI)
-h, --help Show help information.
```
Expand Down Expand Up @@ -53,6 +53,6 @@ Select your project target on Xcode > go to `Build Phases` tab > Press on `+` >
Copy & paste below command on your new script phase and update paths & platform parameters according to your needs.

```
chroma --asset MyProject/Assets.xcassets --path MyProject/Extensions/Colors.swift --platform swiftUI
chroma --asset MyProject/Assets.xcassets --path MyProject/Extensions/Colors.swift --framework SwiftUI
```
Optionally you can rename your new `Run Script` to `Chroma`.
46 changes: 11 additions & 35 deletions Sources/Chroma/App/Chroma.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
//

import ArgumentParser
import Files
import Foundation

public struct Chroma: ParsableCommand {
Expand All @@ -20,48 +19,25 @@ public struct Chroma: ParsableCommand {
@Option(name: .shortAndLong, help: OutputType.help)
private var type: OutputType = .extension

@Option(name: .long, help: "Specifies the platform compatibility of the exported file.\niOS, macOS, swiftUI")
private var platform: Platform = .iOS
@Option(name: .long, help: Framework.help)
private var framework: Framework = .SwiftUI

public init() {}

public func run() throws {
let outputFile = try createOutputFile()
let content = try getContentFromAssetsFile(outputFile: outputFile)
try outputFile.write(content)
let generator = FileGenerator(
asset: asset,
path: path,
type: type,
framework: framework
)
let file = try generator.generate()
print(
"""
\(outputFile.name) was generated successfully.
Can be found at \(outputFile.path)
\(file.name) was generated successfully.
Can be found at \(file.path)
"""
)
}

}

extension Chroma {
private func createOutputFile() throws -> File {
// Check if path param is a valid swift file path
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 try File(named: pathURL.lastPathComponent, at: folder)
}

private func getContentFromAssetsFile(outputFile: File) throws -> String {
let assetFolder = try Folder(path: asset)
let body = platform.fileBody(asset: assetFolder).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)"
}
}
}
Original file line number Diff line number Diff line change
@@ -1,65 +1,44 @@
//
// Platform.swift
// FileGenerator.swift
// Chroma
//
// Created by Oscar De Moya on 7/06/20.
// Copyright © 2020 Jota Uribe. All rights reserved.
// Created by Jota Uribe on 16/10/23.
//

import Foundation
import ArgumentParser
import Files
import Foundation

enum Platform: String, ExpressibleByArgument {
case iOS
case macOS
case swiftUI
}

extension Platform {
struct FileGenerator {
private static let colorAssetExtension = "colorset"

var framework: String {
switch self {
case .iOS: return "UIKit"
case .macOS: return "AppKit"
case .swiftUI: return "SwiftUI"
}
}
let asset: String
let path: String
let type: OutputType
let framework: Framework

var defaultValue: String {
switch self {
case .iOS, .macOS:
return "?? .clear "
case .swiftUI:
return ""
}
func generate() throws -> File {
let outputFile = try createOutputFile()
let content = try getContentFromAssetsFile(outputFile: outputFile)
try outputFile.write(content)
return outputFile
}

var parameterName: String {
switch self {
case .iOS, .macOS:
return "named: "
case .swiftUI:
return ""
}
}
// MARK: Helper Methods

var variableType: String {
switch self {
case .iOS: return "UIColor"
case .macOS: return "NSColor"
case .swiftUI: return "Color"
private func createOutputFile() throws -> File {
// Check if path param is a valid swift file path
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 try File(named: pathURL.lastPathComponent, at: folder)
}

var systemReservedVariableNames: [String] {
switch self {
case .iOS, .macOS:
return []
case .swiftUI:
return ["accentColor"]
}
private func getContentFromAssetsFile(outputFile: File) throws -> String {
let assetFolder = try Folder(path: asset)
let body = fileBody(asset: assetFolder).joined(separator: "\n")
return fileContent(header: header(fileName: outputFile.nameExcludingExtension), body: body)
}

func fileContent(header: String, body: String) -> String {
Expand All @@ -70,8 +49,8 @@ extension Platform {
//
// This file was auto generated please do not modify it directly.
//
import \(framework)
import \(framework.rawValue)
\(header) {
Expand All @@ -81,6 +60,15 @@ extension Platform {
"""
}

func header(fileName: String) -> String {
switch type {
case .extension:
return "\(type.rawValue) \(framework.variableType)"
case .struct:
return "\(type.rawValue) \(fileName.capitalized)"
}
}

func fileBody(asset: Folder) -> Array<String> {
let assetKey = asset.nameExcludingExtension
// Get subfolders with valid extension
Expand All @@ -107,13 +95,7 @@ extension Platform {
private func colorVariableNames(folders: [Folder]) -> [String] {
// We filter out duplicated variable names
Set(folders.compactMap { colorFolder in
return colorVariable(name: colorFolder.nameExcludingExtension)
return framework.colorVariable(name: colorFolder.nameExcludingExtension)
}).sorted()
}

func colorVariable(name: String) -> String? {
let formattedName = name.camelCased().removing(.punctuationCharacters.union(.symbols))
guard !systemReservedVariableNames.contains(formattedName) else { return nil }
return " static var \(formattedName): \(variableType) { return \(variableType)(\(parameterName)\"\(name)\") \(defaultValue)}"
}
}
67 changes: 67 additions & 0 deletions Sources/Chroma/App/Framework.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//
// Framework.swift
// Chroma
//
// Created by Jota Uribe on 16/10/23.
//

import Foundation
import ArgumentParser

enum Framework: String, CaseIterable, ExpressibleByArgument {
case AppKit
case SwiftUI
case UIKit
}

extension Framework {
static var help: ArgumentHelp {
"""
The framework compatibility of generated .swift file.
Supported values: \(formattedValues).
"""
}
}

extension Framework {
var defaultValue: String {
switch self {
case .UIKit, .AppKit:
return "?? .clear "
case .SwiftUI:
return ""
}
}

var parameterName: String {
switch self {
case .UIKit, .AppKit:
return "named: "
case .SwiftUI:
return ""
}
}

var variableType: String {
switch self {
case .UIKit: return "UIColor"
case .AppKit: return "NSColor"
case .SwiftUI: return "Color"
}
}

var systemReservedVariableNames: [String] {
switch self {
case .UIKit, .AppKit:
return []
case .SwiftUI:
return ["accentColor"]
}
}

func colorVariable(name: String) -> String? {
let formattedName = name.camelCased().removing(.punctuationCharacters.union(.symbols))
guard !systemReservedVariableNames.contains(formattedName) else { return nil }
return " static var \(formattedName): \(variableType) { return \(variableType)(\(parameterName)\"\(name)\") \(defaultValue)}"
}
}
7 changes: 1 addition & 6 deletions Sources/Chroma/App/OutputType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,9 @@ enum OutputType: String, CaseIterable, ExpressibleByArgument {
}

extension OutputType {

private static var formattedValues: String {
return OutputType.allCases.map { "\"\($0.rawValue)\"" }.joined(separator: ",")
}

static var help: ArgumentHelp {
"""
Specifies generated file type.
The output type of generated .swift file.
Supported values: \(formattedValues).
"""
}
Expand Down
14 changes: 14 additions & 0 deletions Sources/Chroma/Extensions/CaseIterable+Formatters.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// CaseIterable+Formatters.swift
// Chroma
//
// Created by Jota Uribe on 17/10/23.
//

import Foundation

extension CaseIterable where Self: RawRepresentable {
static var formattedValues: String {
return Self.allCases.map { "\($0.rawValue)" }.joined(separator: ", ")
}
}
Loading

0 comments on commit d696be7

Please sign in to comment.