From 4deb92143586b1037905b2fe7e946fc3fe2742eb Mon Sep 17 00:00:00 2001 From: Timur Shafigullin Date: Wed, 12 Jul 2023 19:04:50 +0300 Subject: [PATCH 1/5] =?UTF-8?q?=D0=93=D0=B5=D0=BD=D0=B5=D1=80=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20=D1=86=D0=B2=D0=B5=D1=82=D0=BE=D0=B2=D1=8B=D1=85?= =?UTF-8?q?=20=D1=82=D0=BE=D0=BA=D0=B5=D0=BD=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GenerationConfigurableCommand.swift | 2 +- Sources/FigmaGen/Commands/TokensCommand.swift | 94 ++++++++++------ Sources/FigmaGen/Dependencies.swift | 11 +- .../Tokens/Contexts/ColorToken.swift | 11 ++ .../Tokens/DefaultTokensGenerator.swift | 45 +++----- ...ltTokensGenerationParametersResolver.swift | 74 +++++++++++++ .../TokensGenerationParametersResolver.swift | 8 ++ .../Color/ColorTokensGenerator.swift | 8 ++ .../Color/DefaultColorTokensGenerator.swift | 102 ++++++++++++++++++ .../Resolver/DefaultTokensResolver.swift | 6 ++ .../Tokens/Resolver/TokensResolver.swift | 18 ++++ .../Tokens/TokensGeneratorError.swift | 5 + .../Tokens/TokensConfiguration.swift | 39 +++++++ .../Tokens/TokensTemplateConfiguration.swift | 57 ++++++++++ .../Configuration/TokensConfiguration.swift | 3 - .../TokensGenerationParameters.swift | 18 ++++ .../Render/DefaultTemplateRenderer.swift | 20 +++- .../FigmaGen/Render/TemplateRenderer.swift | 6 ++ Templates/ColorTokens.stencil | 41 +++++++ Tests/FigmaGenTests/TokensResolverTests.swift | 33 ++++++ 20 files changed, 529 insertions(+), 72 deletions(-) create mode 100644 Sources/FigmaGen/Generators/Tokens/Contexts/ColorToken.swift create mode 100644 Sources/FigmaGen/Generators/Tokens/GenerationParametersResolver/DefaultTokensGenerationParametersResolver.swift create mode 100644 Sources/FigmaGen/Generators/Tokens/GenerationParametersResolver/TokensGenerationParametersResolver.swift create mode 100644 Sources/FigmaGen/Generators/Tokens/Generators/Color/ColorTokensGenerator.swift create mode 100644 Sources/FigmaGen/Generators/Tokens/Generators/Color/DefaultColorTokensGenerator.swift create mode 100644 Sources/FigmaGen/Models/Configuration/Tokens/TokensConfiguration.swift create mode 100644 Sources/FigmaGen/Models/Configuration/Tokens/TokensTemplateConfiguration.swift delete mode 100644 Sources/FigmaGen/Models/Configuration/TokensConfiguration.swift create mode 100644 Sources/FigmaGen/Models/Parameters/TokensGenerationParameters.swift create mode 100644 Templates/ColorTokens.stencil diff --git a/Sources/FigmaGen/Commands/GenerationConfigurableCommand.swift b/Sources/FigmaGen/Commands/GenerationConfigurableCommand.swift index ef85ba2..57f1a70 100644 --- a/Sources/FigmaGen/Commands/GenerationConfigurableCommand.swift +++ b/Sources/FigmaGen/Commands/GenerationConfigurableCommand.swift @@ -75,5 +75,5 @@ extension String { // MARK: - Type Properties - fileprivate static let templateOptionSeparator = ":" + static let templateOptionSeparator = ":" } diff --git a/Sources/FigmaGen/Commands/TokensCommand.swift b/Sources/FigmaGen/Commands/TokensCommand.swift index 687e42f..4ab99e1 100644 --- a/Sources/FigmaGen/Commands/TokensCommand.swift +++ b/Sources/FigmaGen/Commands/TokensCommand.swift @@ -1,6 +1,6 @@ import SwiftCLI -final class TokensCommand: AsyncExecutableCommand, GenerationConfigurableCommand { +final class TokensCommand: AsyncExecutableCommand { // MARK: - Instance Properties @@ -25,25 +25,6 @@ final class TokensCommand: AsyncExecutableCommand, GenerationConfigurableCommand """ ) - let includedNodes = VariadicKey( - "--includingNodes", - "-i", - description: #""" - A list of Figma nodes whose styles will be extracted. - Can be repeated multiple times and must be in the format: -i "1:23". - If omitted, all nodes will be included. - """# - ) - - let excludedNodes = VariadicKey( - "--excludingNodes", - "-e", - description: #""" - A list of Figma nodes whose styles will be ignored. - Can be repeated multiple times and must be in the format: -e "1:23". - """# - ) - let accessToken = Key( "--accessToken", description: """ @@ -52,27 +33,24 @@ final class TokensCommand: AsyncExecutableCommand, GenerationConfigurableCommand """ ) - let template = Key( - "--template", - "-t", + let colorsTemplate = Key( + "--colors-template", description: """ Path to the template file. If no template is passed a default template will be used. """ ) - let templateOptions = VariadicKey( - "--options", - "-o", + let colorsTemplateOptions = VariadicKey( + "--colors-options", description: #""" An option that will be merged with template context, and overwrite any values of the same name. Can be repeated multiple times and must be in the format: -o "name:value". """# ) - let destination = Key( - "--destination", - "-d", + let colorsDestination = Key( + "--colors-destination", description: """ The path to the file to generate. By default, generated code will be printed on stdout. @@ -89,10 +67,66 @@ final class TokensCommand: AsyncExecutableCommand, GenerationConfigurableCommand func executeAsyncAndExit() async throws { do { - try await generator.generate(configuration: generationConfiguration) + try await generator.generate(configuration: configuration) succeed(message: "Tokens generated successfully!") } catch { fail(message: "Failed to generate tokens: \(error)") } } } + +extension TokensCommand { + + // MARK: - Instance Properties + + var configuration: TokensConfiguration { + TokensConfiguration( + file: resolveFileConfiguration(), + accessToken: resolveAccessTokenConfiguration(), + templates: TokensTemplateConfiguration( + color: TokensTemplateConfiguration.Template( + template: colorsTemplate.value, + templateOptions: resolveTemplateOptions(colorsTemplateOptions.value), + destination: colorsDestination.value + ) + ) + ) + } + + // MARK: - Instance Methods + + private func resolveFileConfiguration() -> FileConfiguration? { + guard let fileKey = fileKey.value else { + return nil + } + + return FileConfiguration( + key: fileKey, + version: fileVersion.value, + includedNodes: nil, + excludedNodes: nil + ) + } + + private func resolveAccessTokenConfiguration() -> AccessTokenConfiguration? { + guard let accessToken = accessToken.value else { + return nil + } + + return .value(accessToken) + } + + private func resolveTemplateOptions(_ templateOptionsValues: [String]) -> [String: Any] { + var templateOptions: [String: String] = [:] + + for templateOption in templateOptionsValues { + var optionComponents = templateOption.components(separatedBy: String.templateOptionSeparator) + let optionKey = optionComponents.removeFirst().trimmingCharacters(in: .whitespaces) + let optionValue = optionComponents.joined(separator: .templateOptionSeparator) + + templateOptions[optionKey] = optionValue + } + + return templateOptions + } +} diff --git a/Sources/FigmaGen/Dependencies.swift b/Sources/FigmaGen/Dependencies.swift index 2510b30..45582ed 100644 --- a/Sources/FigmaGen/Dependencies.swift +++ b/Sources/FigmaGen/Dependencies.swift @@ -64,6 +64,9 @@ enum Dependencies { static let tokensResolver: TokensResolver = DefaultTokensResolver() + static let tokensGenerationParametersResolver: TokensGenerationParametersResolver + = DefaultTokensGenerationParametersResolver() + // MARK: - static let templateContextCoder: TemplateContextCoder = DefaultTemplateContextCoder() @@ -119,8 +122,14 @@ enum Dependencies { shadowStylesGenerator: shadowStylesGenerator ) + static let colorTokensGenerator: ColorTokensGenerator = DefaultColorTokensGenerator( + tokensResolver: tokensResolver, + templateRenderer: templateRenderer + ) + static let tokensGenerator: TokensGenerator = DefaultTokensGenerator( tokensProvider: tokensProvider, - tokensResolver: tokensResolver + tokensGenerationParametersResolver: tokensGenerationParametersResolver, + colorTokensGenerator: colorTokensGenerator ) } diff --git a/Sources/FigmaGen/Generators/Tokens/Contexts/ColorToken.swift b/Sources/FigmaGen/Generators/Tokens/Contexts/ColorToken.swift new file mode 100644 index 0000000..e78ab9b --- /dev/null +++ b/Sources/FigmaGen/Generators/Tokens/Contexts/ColorToken.swift @@ -0,0 +1,11 @@ +import Foundation + +struct ColorToken: Encodable { + + // MARK: - Instance Properties + + let dayValue: String + let nightValue: String + let name: String + let path: [String] +} diff --git a/Sources/FigmaGen/Generators/Tokens/DefaultTokensGenerator.swift b/Sources/FigmaGen/Generators/Tokens/DefaultTokensGenerator.swift index 712dd0a..87bdcc3 100644 --- a/Sources/FigmaGen/Generators/Tokens/DefaultTokensGenerator.swift +++ b/Sources/FigmaGen/Generators/Tokens/DefaultTokensGenerator.swift @@ -2,58 +2,39 @@ import Foundation import FigmaGenTools import Expression -final class DefaultTokensGenerator: TokensGenerator, GenerationParametersResolving { +final class DefaultTokensGenerator: TokensGenerator { // MARK: - Instance Properties let tokensProvider: TokensProvider - let tokensResolver: TokensResolver - - let defaultTemplateType = RenderTemplateType.native(name: "Tokens") - let defaultDestination = RenderDestination.console + let tokensGenerationParametersResolver: TokensGenerationParametersResolver + let colorTokensGenerator: ColorTokensGenerator // MARK: - Initializers - init(tokensProvider: TokensProvider, tokensResolver: TokensResolver) { + init( + tokensProvider: TokensProvider, + tokensGenerationParametersResolver: TokensGenerationParametersResolver, + colorTokensGenerator: ColorTokensGenerator + ) { self.tokensProvider = tokensProvider - self.tokensResolver = tokensResolver + self.tokensGenerationParametersResolver = tokensGenerationParametersResolver + self.colorTokensGenerator = colorTokensGenerator } // MARK: - Instance Methods - private func generate(parameters: GenerationParameters) async throws { + private func generate(parameters: TokensGenerationParameters) async throws { let tokenValues = try await tokensProvider.fetchTokens(from: parameters.file) - try tokenValues.all - .compactMap { tokenValue -> (TokenValue, String)? in - guard let value = tokenValue.type.stringValue else { - return nil - } - - return (tokenValue, try tokensResolver.resolveValue(value, tokenValues: tokenValues)) - } - .forEach { tokenValue, value in - if value.hasPrefix("rgba") { - let color = try tokensResolver.resolveRGBAColorValue(value, tokenValues: tokenValues) - - print("[\(tokenValue.name)] \(color)") - } else if value.hasPrefix("linear-gradient") { - let gradient = try tokensResolver.resolveLinearGradientValue(value, tokenValues: tokenValues) - - print("[\(tokenValue.name)] \(gradient)") - } else { - print("[\(tokenValue.name)] \(value)") - } - } - - // PORTFOLIO-22826 Генерация основных токенов + try colorTokensGenerator.generate(renderParameters: parameters.tokens.colorRender, tokenValues: tokenValues) } // MARK: - func generate(configuration: TokensConfiguration) async throws { let parameters = try await Task.detached(priority: .userInitiated) { - try self.resolveGenerationParameters(from: configuration) + try self.tokensGenerationParametersResolver.resolveGenerationParameters(from: configuration) }.value try await generate(parameters: parameters) diff --git a/Sources/FigmaGen/Generators/Tokens/GenerationParametersResolver/DefaultTokensGenerationParametersResolver.swift b/Sources/FigmaGen/Generators/Tokens/GenerationParametersResolver/DefaultTokensGenerationParametersResolver.swift new file mode 100644 index 0000000..83bfde2 --- /dev/null +++ b/Sources/FigmaGen/Generators/Tokens/GenerationParametersResolver/DefaultTokensGenerationParametersResolver.swift @@ -0,0 +1,74 @@ +import Foundation + +final class DefaultTokensGenerationParametersResolver: TokensGenerationParametersResolver { + + // MARK: - Instance Methods + + private func resolveAccessToken(configuration: TokensConfiguration) -> String? { + switch configuration.accessToken { + case let .value(accessToken): + return accessToken + + case let .environmentVariable(environmentVariable): + return ProcessInfo.processInfo.environment[environmentVariable] + + case nil: + return nil + } + } + + private func resolveColorTemplateType(configuration: TokensConfiguration) -> RenderTemplateType { + if let templatePath = configuration.templates?.color?.template { + return .custom(path: templatePath) + } + + return .native(name: "ColorTokens") + } + + private func resolveColorDestination(configuration: TokensConfiguration) -> RenderDestination { + if let destinationPath = configuration.templates?.color?.destination { + return .file(path: destinationPath) + } + + return .console + } + + private func resolveColorRenderParameters(configuration: TokensConfiguration) -> RenderParameters { + let templateType = resolveColorTemplateType(configuration: configuration) + let destination = resolveColorDestination(configuration: configuration) + + let template = RenderTemplate( + type: templateType, + options: configuration.templates?.color?.templateOptions ?? [:] + ) + + return RenderParameters(template: template, destination: destination) + } + + // MARK: - + + func resolveGenerationParameters(from configuration: TokensConfiguration) throws -> TokensGenerationParameters { + guard let fileConfiguration = configuration.file else { + throw GenerationParametersError.invalidFileConfiguration + } + + guard let accessToken = resolveAccessToken(configuration: configuration) else { + throw GenerationParametersError.invalidAccessToken + } + + let file = FileParameters( + key: fileConfiguration.key, + version: fileConfiguration.version, + accessToken: accessToken + ) + + let colorRender = resolveColorRenderParameters(configuration: configuration) + + return TokensGenerationParameters( + file: file, + tokens: TokensGenerationParameters.TokensParameters( + colorRender: colorRender + ) + ) + } +} diff --git a/Sources/FigmaGen/Generators/Tokens/GenerationParametersResolver/TokensGenerationParametersResolver.swift b/Sources/FigmaGen/Generators/Tokens/GenerationParametersResolver/TokensGenerationParametersResolver.swift new file mode 100644 index 0000000..f3d73e5 --- /dev/null +++ b/Sources/FigmaGen/Generators/Tokens/GenerationParametersResolver/TokensGenerationParametersResolver.swift @@ -0,0 +1,8 @@ +import Foundation + +protocol TokensGenerationParametersResolver { + + // MARK: - Instance Methods + + func resolveGenerationParameters(from configuration: TokensConfiguration) throws -> TokensGenerationParameters +} diff --git a/Sources/FigmaGen/Generators/Tokens/Generators/Color/ColorTokensGenerator.swift b/Sources/FigmaGen/Generators/Tokens/Generators/Color/ColorTokensGenerator.swift new file mode 100644 index 0000000..62156e2 --- /dev/null +++ b/Sources/FigmaGen/Generators/Tokens/Generators/Color/ColorTokensGenerator.swift @@ -0,0 +1,8 @@ +import Foundation + +protocol ColorTokensGenerator { + + // MARK: - Instance Methods + + func generate(renderParameters: RenderParameters, tokenValues: TokenValues) throws +} diff --git a/Sources/FigmaGen/Generators/Tokens/Generators/Color/DefaultColorTokensGenerator.swift b/Sources/FigmaGen/Generators/Tokens/Generators/Color/DefaultColorTokensGenerator.swift new file mode 100644 index 0000000..5d0ddf2 --- /dev/null +++ b/Sources/FigmaGen/Generators/Tokens/Generators/Color/DefaultColorTokensGenerator.swift @@ -0,0 +1,102 @@ +import Foundation + +final class DefaultColorTokensGenerator: ColorTokensGenerator { + + // MARK: - Instance Properties + + let tokensResolver: TokensResolver + let templateRenderer: TemplateRenderer + + // MARK: - Initializers + + init(tokensResolver: TokensResolver, templateRenderer: TemplateRenderer) { + self.tokensResolver = tokensResolver + self.templateRenderer = templateRenderer + } + + // MARK: - Instance Methods + + private func resolveNightValue( + tokenName: String, + fallbackValue: String, + tokenValues: TokenValues + ) throws -> String { + guard let nightToken = tokenValues.night.first(where: { $0.name == tokenName }) else { + return fallbackValue + } + + guard case .color(let nightValue) = nightToken.type else { + return fallbackValue + } + + return try tokensResolver.resolveHexColorValue(nightValue, tokenValues: tokenValues) + } + + private func structure(tokenColors: [ColorToken], atNamePath namePath: [String] = []) -> [String: Any] { + var structuredColors: [String: Any] = [:] + + if let name = namePath.last { + structuredColors["name"] = name + } + + let colors = tokenColors + .filter { $0.path.count == namePath.count + 1 } + .sorted { $0.name.lowercased() < $1.name.lowercased() } + + if !colors.isEmpty { + structuredColors["colors"] = colors + } + + let childTokenColors = tokenColors.filter { $0.path.count > namePath.count + 1 } + + let children = Dictionary(grouping: childTokenColors) { $0.path[namePath.count] } + .sorted { $0.key < $1.key } + .map { name, colors in + structure(tokenColors: colors, atNamePath: namePath + [name]) + } + + if !children.isEmpty { + structuredColors["children"] = children + } + + return structuredColors + } + + // MARK: - + + func generate(renderParameters: RenderParameters, tokenValues: TokenValues) throws { + let colors: [ColorToken] = try tokenValues.day.compactMap { (token: TokenValue) in + guard case .color(let dayValue) = token.type else { + return nil + } + + let path = token.name.components(separatedBy: ".") + + guard path[0] == "color" && path[1] != "component" else { + return nil + } + + return ColorToken( + dayValue: try tokensResolver.resolveHexColorValue( + dayValue, + tokenValues: tokenValues + ), + nightValue: try resolveNightValue( + tokenName: token.name, + fallbackValue: dayValue, + tokenValues: tokenValues + ), + name: token.name, + path: path.removingFirst() + ) + } + + let structuredColors = structure(tokenColors: colors) + + try templateRenderer.renderTemplate( + renderParameters.template, + to: renderParameters.destination, + context: ["colorTokens": structuredColors] + ) + } +} diff --git a/Sources/FigmaGen/Generators/Tokens/Resolver/DefaultTokensResolver.swift b/Sources/FigmaGen/Generators/Tokens/Resolver/DefaultTokensResolver.swift index dcf924f..8f0aeff 100644 --- a/Sources/FigmaGen/Generators/Tokens/Resolver/DefaultTokensResolver.swift +++ b/Sources/FigmaGen/Generators/Tokens/Resolver/DefaultTokensResolver.swift @@ -87,6 +87,12 @@ final class DefaultTokensResolver: TokensResolver { return try makeColor(hex: hex, alpha: alpha / 100.0) } + func resolveHexColorValue(_ value: String, tokenValues: TokenValues) throws -> String { + let resolvedValue = try resolveValue(value, tokenValues: tokenValues) + + return try resolveColorValue(resolvedValue, tokenValues: tokenValues).hexString + } + func resolveLinearGradientValue(_ value: String, tokenValues: TokenValues) throws -> LinearGradient { let value = try resolveValue(value, tokenValues: tokenValues) diff --git a/Sources/FigmaGen/Generators/Tokens/Resolver/TokensResolver.swift b/Sources/FigmaGen/Generators/Tokens/Resolver/TokensResolver.swift index 4732219..73f0282 100644 --- a/Sources/FigmaGen/Generators/Tokens/Resolver/TokensResolver.swift +++ b/Sources/FigmaGen/Generators/Tokens/Resolver/TokensResolver.swift @@ -37,6 +37,24 @@ protocol TokensResolver { /// - Returns: ``Color`` object with values resolved from `rgba()` func resolveRGBAColorValue(_ value: String, tokenValues: TokenValues) throws -> Color + /// Resolving references and mathematical expressions in `value` using ``resolveValue(_:tokenValues:)`` + /// and convert `rgba()` to hex value + /// + /// See ``resolveRGBAColorValue(_:tokenValues:)`` for supported formats for `rgba()` + /// + /// - Parameters: + /// - value: Raw `rgba()` with references, e.g.: + /// ``` + /// rgba( + /// {color.base.white}, + /// {semantic.opacity.disabled} + /// ) + /// ``` + /// Or simple reference to another color: `{color.base.white}` + /// - tokenValues: All token values + /// - Returns: Hex value of the color + func resolveHexColorValue(_ value: String, tokenValues: TokenValues) throws -> String + /// Resolving references and mathematical expressions in `value` using ``resolveValue(_:tokenValues:)`` /// and convert `linear-gradient()` to ``LinearGradient`` object /// diff --git a/Sources/FigmaGen/Generators/Tokens/TokensGeneratorError.swift b/Sources/FigmaGen/Generators/Tokens/TokensGeneratorError.swift index fbb109b..e7d4542 100644 --- a/Sources/FigmaGen/Generators/Tokens/TokensGeneratorError.swift +++ b/Sources/FigmaGen/Generators/Tokens/TokensGeneratorError.swift @@ -12,6 +12,8 @@ struct TokensGeneratorError: Error, CustomStringConvertible { case invalidAlphaComponent(alpha: String) case invalidHEXComponent(hex: String) case failedToExtractLinearGradientParams(linearGradient: String) + + case nightColorNotFound(tokenName: String) } // MARK: - Instance Properties @@ -42,6 +44,9 @@ struct TokensGeneratorError: Error, CustomStringConvertible { case .failedToExtractLinearGradientParams(let linearGradient): return "Failed to extract linear gradient parameters: \(linearGradient)" + + case .nightColorNotFound(let tokenName): + return "Night color for token '\(tokenName)' not found" } } } diff --git a/Sources/FigmaGen/Models/Configuration/Tokens/TokensConfiguration.swift b/Sources/FigmaGen/Models/Configuration/Tokens/TokensConfiguration.swift new file mode 100644 index 0000000..a430b5d --- /dev/null +++ b/Sources/FigmaGen/Models/Configuration/Tokens/TokensConfiguration.swift @@ -0,0 +1,39 @@ +import Foundation + +struct TokensConfiguration: Decodable { + + // MARK: - Nested Types + + private enum CodingKeys: String, CodingKey { + case templates + } + + // MARK: - Instance Properties + + let file: FileConfiguration? + let accessToken: AccessTokenConfiguration? + let templates: TokensTemplateConfiguration? + + // MARK: - Initializers + + init(from decoder: Decoder) throws { + let base = try BaseConfiguration(from: decoder) + + self.file = base.file + self.accessToken = base.accessToken + + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.templates = try container.decodeIfPresent(forKey: .templates) + } + + init( + file: FileConfiguration?, + accessToken: AccessTokenConfiguration?, + templates: TokensTemplateConfiguration? + ) { + self.file = file + self.accessToken = accessToken + self.templates = templates + } +} diff --git a/Sources/FigmaGen/Models/Configuration/Tokens/TokensTemplateConfiguration.swift b/Sources/FigmaGen/Models/Configuration/Tokens/TokensTemplateConfiguration.swift new file mode 100644 index 0000000..8f8c686 --- /dev/null +++ b/Sources/FigmaGen/Models/Configuration/Tokens/TokensTemplateConfiguration.swift @@ -0,0 +1,57 @@ +import Foundation +import FigmaGenTools + +struct TokensTemplateConfiguration: Decodable { + + // MARK: - Nested Types + + struct Template: Decodable { + + // MARK: - Instance Properties + + let template: String? + let templateOptions: [String: Any]? + let destination: String? + + // MARK: - Initializers + + init( + template: String?, + templateOptions: [String: Any]?, + destination: String? + ) { + self.template = template + self.templateOptions = templateOptions + self.destination = destination + } + } + + // MARK: - Instance Properties + + let color: Template? +} + +extension TokensTemplateConfiguration.Template { + + // MARK: - Nested Types + + private enum CodingKeys: String, CodingKey { + case template + case templateOptions + case destination + } + + // MARK: - Initializers + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.template = try container.decodeIfPresent(forKey: .template) + + self.templateOptions = try container + .decodeIfPresent([String: AnyCodable].self, forKey: .templateOptions)? + .mapValues { $0.value } + + self.destination = try container.decodeIfPresent(forKey: .destination) + } +} diff --git a/Sources/FigmaGen/Models/Configuration/TokensConfiguration.swift b/Sources/FigmaGen/Models/Configuration/TokensConfiguration.swift deleted file mode 100644 index e99efe6..0000000 --- a/Sources/FigmaGen/Models/Configuration/TokensConfiguration.swift +++ /dev/null @@ -1,3 +0,0 @@ -import Foundation - -typealias TokensConfiguration = GenerationConfiguration diff --git a/Sources/FigmaGen/Models/Parameters/TokensGenerationParameters.swift b/Sources/FigmaGen/Models/Parameters/TokensGenerationParameters.swift new file mode 100644 index 0000000..8fb38aa --- /dev/null +++ b/Sources/FigmaGen/Models/Parameters/TokensGenerationParameters.swift @@ -0,0 +1,18 @@ +import Foundation + +struct TokensGenerationParameters { + + // MARK: - Nested Types + + struct TokensParameters { + + // MARK: - Instance Properties + + let colorRender: RenderParameters + } + + // MARK: - Instance Properties + + let file: FileParameters + let tokens: TokensParameters +} diff --git a/Sources/FigmaGen/Render/DefaultTemplateRenderer.swift b/Sources/FigmaGen/Render/DefaultTemplateRenderer.swift index 6ebe1ee..27b4ed4 100644 --- a/Sources/FigmaGen/Render/DefaultTemplateRenderer.swift +++ b/Sources/FigmaGen/Render/DefaultTemplateRenderer.swift @@ -68,10 +68,10 @@ final class DefaultTemplateRenderer: TemplateRenderer { // MARK: - - func renderTemplate( + func renderTemplate( _ template: RenderTemplate, to destination: RenderDestination, - context: Context + context: [String: Any] ) throws { let stencilExtensionRegistry = ExtensionRegistry() @@ -96,14 +96,24 @@ final class DefaultTemplateRenderer: TemplateRenderer { environment: stencilEnvironment ) - let templateContext = try contextCoder - .encode(context) - .merging([.templateOptionsKey: template.options]) { $1 } + let templateContext = context.merging([.templateOptionsKey: template.options]) { $1 } let output = try stencilTemplate.render(templateContext) try writeOutput(output, to: destination) } + + func renderTemplate( + _ template: RenderTemplate, + to destination: RenderDestination, + context: Context + ) throws { + try renderTemplate( + template, + to: destination, + context: try contextCoder.encode(context) + ) + } } extension String { diff --git a/Sources/FigmaGen/Render/TemplateRenderer.swift b/Sources/FigmaGen/Render/TemplateRenderer.swift index 60930ba..f42705f 100644 --- a/Sources/FigmaGen/Render/TemplateRenderer.swift +++ b/Sources/FigmaGen/Render/TemplateRenderer.swift @@ -4,6 +4,12 @@ protocol TemplateRenderer { // MARK: - Instance Methods + func renderTemplate( + _ template: RenderTemplate, + to destination: RenderDestination, + context: [String: Any] + ) throws + func renderTemplate( _ template: RenderTemplate, to destination: RenderDestination, diff --git a/Templates/ColorTokens.stencil b/Templates/ColorTokens.stencil new file mode 100644 index 0000000..698d08b --- /dev/null +++ b/Templates/ColorTokens.stencil @@ -0,0 +1,41 @@ +// swiftlint:disable all +// Generated using FigmaGen - https://github.com/hhru/FigmaGen +{% set colorTypeName %}{{ options.colorTypeName|default:"UIColor" }}{% endset %} +{% set accessModifier %}{% if options.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set tokenTypeName %}{{ options.tokenTypeName|default:"ColorTokens" }}{% endset %} +{% macro propertyName name %}{{ name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords }}{% endmacro %} +{% macro typeName name %}{{ name|swiftIdentifier:"pretty"|upperFirstLetter|escapeReservedKeywords }}{% endmacro %} +{% macro recursiveBlock item %} + {% for color in item.colors %} + + /// {{ color.name }} + /// + /// Day: {{ color.dayValue }} + /// Night: {{ color.nightValue }} + public let {% call propertyName color.path.last %}: {{ colorTypeName }} + {% endfor %} + {% for child in item.children %} + + public struct {% call typeName child.name %} { + {% filter indent:4 %} + {% call recursiveBlock child %} + {% endfilter %} + } + + public let {% call propertyName child.name %}: {% call typeName child.name %} + {% endfor %} +{% endmacro %} + +#if canImport(UIKit) +import UIKit +#else +import AppKit +#endif + +{{ accessModifier }} struct {{ tokenTypeName }} { + {% if colorTokens %} + {% call recursiveBlock colorTokens %} + {% else %} + // No color tokens found + {% endif %} +} diff --git a/Tests/FigmaGenTests/TokensResolverTests.swift b/Tests/FigmaGenTests/TokensResolverTests.swift index afed373..287768c 100644 --- a/Tests/FigmaGenTests/TokensResolverTests.swift +++ b/Tests/FigmaGenTests/TokensResolverTests.swift @@ -171,6 +171,39 @@ final class TokensResolverTests: XCTestCase { XCTAssertEqual(actualLinearGradient, expectedLinearGradient) } + + func testResolveHexColorWithReferences() throws { + let tokenValues = TokenValues( + core: [ + TokenValue(type: .opacity(value: "48%"), name: "core.opacity.48") + ], + semantic: [ + TokenValue(type: .opacity(value: "{core.opacity.48}"), name: "semantic.opacity.disabled") + ], + colors: [ + TokenValue(type: .core(value: "#ffffff"), name: "color.base.white") + ], + typography: [], + day: [], + night: [] + ) + + let value = "rgba({color.base.white}, {semantic.opacity.disabled})" + let expectedHexColor = "#FFFFFF7A" + + let actualHexColor = try tokensResolver.resolveHexColorValue(value, tokenValues: tokenValues) + + XCTAssertEqual(actualHexColor, expectedHexColor) + } + + func testResolveHexColorWithoutReferences() throws { + let value = "rgba(#FFFFFF, 48%)" + let expectedHexColor = "#FFFFFF7A" + + let actualHexColor = try tokensResolver.resolveHexColorValue(value, tokenValues: .empty) + + XCTAssertEqual(actualHexColor, expectedHexColor) + } } extension TokenValues { From 2093e2ebf632520f8556356b0ae3e9f4bd0443d0 Mon Sep 17 00:00:00 2001 From: Timur Shafigullin Date: Mon, 17 Jul 2023 15:11:03 +0300 Subject: [PATCH 2/5] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=B1=D0=BE=D0=BB=D1=8C=D1=88=D0=B5=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BD=D1=82=D0=B5=D0=BA=D1=81=D1=82=D0=B0=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=B3=D0=B5=D0=BD=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D0=B8?= =?UTF-8?q?=20=D1=86=D0=B2=D0=B5=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/FigmaGen/Dependencies.swift | 5 +- .../Tokens/Contexts/ColorToken.swift | 14 +++- .../Color/DefaultColorTokensGenerator.swift | 68 +++++++++++++++---- ...tencilCollectionDropFirstModificator.swift | 20 ++++++ ...StencilCollectionDropLastModificator.swift | 20 ++++++ .../StencilExtensions/StencilExtension.swift | 38 +++++++++++ .../String/StencilHexToAlphaFilter.swift | 34 ++++++++++ Templates/ColorTokens.stencil | 4 +- 8 files changed, 185 insertions(+), 18 deletions(-) create mode 100644 Sources/FigmaGen/Render/StencilExtensions/Collection/StencilCollectionDropFirstModificator.swift create mode 100644 Sources/FigmaGen/Render/StencilExtensions/Collection/StencilCollectionDropLastModificator.swift create mode 100644 Sources/FigmaGen/Render/StencilExtensions/String/StencilHexToAlphaFilter.swift diff --git a/Sources/FigmaGen/Dependencies.swift b/Sources/FigmaGen/Dependencies.swift index 45582ed..28c332c 100644 --- a/Sources/FigmaGen/Dependencies.swift +++ b/Sources/FigmaGen/Dependencies.swift @@ -84,7 +84,10 @@ enum Dependencies { StencilColorInfoFilter(contextCoder: templateContextCoder), StencilFontInfoFilter(contextCoder: templateContextCoder), StencilFontInitializerModificator(contextCoder: templateContextCoder), - StencilFontSystemFilter(contextCoder: templateContextCoder) + StencilFontSystemFilter(contextCoder: templateContextCoder), + StencilCollectionDropFirstModificator(), + StencilCollectionDropLastModificator(), + StencilHexToAlphaFilter() ] static let templateRenderer: TemplateRenderer = DefaultTemplateRenderer( diff --git a/Sources/FigmaGen/Generators/Tokens/Contexts/ColorToken.swift b/Sources/FigmaGen/Generators/Tokens/Contexts/ColorToken.swift index e78ab9b..3068740 100644 --- a/Sources/FigmaGen/Generators/Tokens/Contexts/ColorToken.swift +++ b/Sources/FigmaGen/Generators/Tokens/Contexts/ColorToken.swift @@ -2,10 +2,20 @@ import Foundation struct ColorToken: Encodable { + // MARK: - Nested Types + + struct Theme: Encodable { + + // MARK: - Instance Properties + + let value: String + let reference: String + } + // MARK: - Instance Properties - let dayValue: String - let nightValue: String + let dayTheme: Theme + let nightTheme: Theme let name: String let path: [String] } diff --git a/Sources/FigmaGen/Generators/Tokens/Generators/Color/DefaultColorTokensGenerator.swift b/Sources/FigmaGen/Generators/Tokens/Generators/Color/DefaultColorTokensGenerator.swift index 5d0ddf2..35780c3 100644 --- a/Sources/FigmaGen/Generators/Tokens/Generators/Color/DefaultColorTokensGenerator.swift +++ b/Sources/FigmaGen/Generators/Tokens/Generators/Color/DefaultColorTokensGenerator.swift @@ -32,6 +32,22 @@ final class DefaultColorTokensGenerator: ColorTokensGenerator { return try tokensResolver.resolveHexColorValue(nightValue, tokenValues: tokenValues) } + private func resolveNightReference( + tokenName: String, + fallbackRefence: String, + tokenValues: TokenValues + ) -> String { + guard let nightToken = tokenValues.night.first(where: { $0.name == tokenName }) else { + return fallbackRefence + } + + guard case .color(let nightValue) = nightToken.type else { + return fallbackRefence + } + + return nightValue + } + private func structure(tokenColors: [ColorToken], atNamePath namePath: [String] = []) -> [String: Any] { var structuredColors: [String: Any] = [:] @@ -62,6 +78,39 @@ final class DefaultColorTokensGenerator: ColorTokensGenerator { return structuredColors } + private func makeColorToken( + dayValue: String, + tokenName: String, + tokenValues: TokenValues, + path: [String] + ) throws -> ColorToken { + let dayHexColorValue = try tokensResolver.resolveHexColorValue( + dayValue, + tokenValues: tokenValues + ) + + return ColorToken( + dayTheme: ColorToken.Theme( + value: dayHexColorValue, + reference: dayValue + ), + nightTheme: ColorToken.Theme( + value: try resolveNightValue( + tokenName: tokenName, + fallbackValue: dayHexColorValue, + tokenValues: tokenValues + ), + reference: resolveNightReference( + tokenName: tokenName, + fallbackRefence: dayValue, + tokenValues: tokenValues + ) + ), + name: tokenName, + path: path.removingFirst() + ) + } + // MARK: - func generate(renderParameters: RenderParameters, tokenValues: TokenValues) throws { @@ -72,22 +121,15 @@ final class DefaultColorTokensGenerator: ColorTokensGenerator { let path = token.name.components(separatedBy: ".") - guard path[0] == "color" && path[1] != "component" else { + guard path[0] == "color" else { return nil } - return ColorToken( - dayValue: try tokensResolver.resolveHexColorValue( - dayValue, - tokenValues: tokenValues - ), - nightValue: try resolveNightValue( - tokenName: token.name, - fallbackValue: dayValue, - tokenValues: tokenValues - ), - name: token.name, - path: path.removingFirst() + return try makeColorToken( + dayValue: dayValue, + tokenName: token.name, + tokenValues: tokenValues, + path: path ) } diff --git a/Sources/FigmaGen/Render/StencilExtensions/Collection/StencilCollectionDropFirstModificator.swift b/Sources/FigmaGen/Render/StencilExtensions/Collection/StencilCollectionDropFirstModificator.swift new file mode 100644 index 0000000..b006f0c --- /dev/null +++ b/Sources/FigmaGen/Render/StencilExtensions/Collection/StencilCollectionDropFirstModificator.swift @@ -0,0 +1,20 @@ +import Foundation + +final class StencilCollectionDropFirstModificator: StencilModificator { + + // MARK: - Instance Properties + + let name = "dropFirst" + + // MARK: - Instance Methods + + func modify(input: Any, withArguments arguments: [Any?]) throws -> Any { + let count = arguments.first as? Int ?? 1 + + if let array = input as? [Any?] { + return Array(array.dropFirst(count)) + } + + return String(stringify(input).dropFirst(count)) + } +} diff --git a/Sources/FigmaGen/Render/StencilExtensions/Collection/StencilCollectionDropLastModificator.swift b/Sources/FigmaGen/Render/StencilExtensions/Collection/StencilCollectionDropLastModificator.swift new file mode 100644 index 0000000..a9711c2 --- /dev/null +++ b/Sources/FigmaGen/Render/StencilExtensions/Collection/StencilCollectionDropLastModificator.swift @@ -0,0 +1,20 @@ +import Foundation + +final class StencilCollectionDropLastModificator: StencilModificator { + + // MARK: - Instance Properties + + let name = "dropLast" + + // MARK: - Instance Methods + + func modify(input: Any, withArguments arguments: [Any?]) throws -> Any { + let count = arguments.first as? Int ?? 1 + + if let array = input as? [Any?] { + return Array(array.dropLast(count)) + } + + return String(stringify(input).dropLast(count)) + } +} diff --git a/Sources/FigmaGen/Render/StencilExtensions/StencilExtension.swift b/Sources/FigmaGen/Render/StencilExtensions/StencilExtension.swift index 2a9f351..aa3182c 100644 --- a/Sources/FigmaGen/Render/StencilExtensions/StencilExtension.swift +++ b/Sources/FigmaGen/Render/StencilExtensions/StencilExtension.swift @@ -11,3 +11,41 @@ protocol StencilExtension { func register(in extensionRegistry: ExtensionRegistry) } + +extension StencilExtension { + + // MARK: - Instance Methods + + private func unwrap(_ array: [Any?]) -> [Any] { + array.map { item in + if let item { + if let items = item as? [Any?] { + return unwrap(items) + } + + return item + } + + return item as Any + } + } + + func stringify(_ result: Any?) -> String { + switch result { + case let result as String: + return result + + case let array as [Any?]: + return unwrap(array).description + + case let result as CustomStringConvertible: + return result.description + + case let result as NSObject: + return result.description + + default: + return .empty + } + } +} diff --git a/Sources/FigmaGen/Render/StencilExtensions/String/StencilHexToAlphaFilter.swift b/Sources/FigmaGen/Render/StencilExtensions/String/StencilHexToAlphaFilter.swift new file mode 100644 index 0000000..34e20a7 --- /dev/null +++ b/Sources/FigmaGen/Render/StencilExtensions/String/StencilHexToAlphaFilter.swift @@ -0,0 +1,34 @@ +import Foundation + +final class StencilHexToAlphaFilter: StencilFilter { + + // MARK: - Instance Properties + + let name = "hexToAlpha" + + // MARK: - Instance Methods + + func filter(input: String) throws -> Double { + guard input.hasPrefix("#") else { + throw StencilFilterError(code: .invalidValue(input), filter: name) + } + + let hexColor = String(input.dropFirst()) + + guard hexColor.count == 8 else { + throw StencilFilterError(code: .invalidValue(input), filter: name) + } + + let scanner = Scanner(string: hexColor) + var hexNumber: UInt64 = 0 + + guard scanner.scanHexInt64(&hexNumber) else { + throw StencilFilterError(code: .invalidValue(input), filter: name) + } + + let alpha = Double(hexNumber & 0x000000ff) / 255 + let multiplier = pow(10.0, 2.0) + + return round(alpha * multiplier) / multiplier + } +} diff --git a/Templates/ColorTokens.stencil b/Templates/ColorTokens.stencil index 698d08b..5ce9665 100644 --- a/Templates/ColorTokens.stencil +++ b/Templates/ColorTokens.stencil @@ -10,8 +10,8 @@ /// {{ color.name }} /// - /// Day: {{ color.dayValue }} - /// Night: {{ color.nightValue }} + /// Day: {{ color.dayTheme.value }} + /// Night: {{ color.nightTheme.value }} public let {% call propertyName color.path.last %}: {{ colorTypeName }} {% endfor %} {% for child in item.children %} From 06f31aa10983f759ab4e584a715a029d7fe9e807 Mon Sep 17 00:00:00 2001 From: Timur Shafigullin Date: Tue, 18 Jul 2023 16:32:55 +0300 Subject: [PATCH 3/5] =?UTF-8?q?=D0=93=D0=B5=D0=BD=D0=B5=D1=80=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B1=D0=B0=D0=B7=D0=BE=D0=B2=D1=8B=D1=85=20?= =?UTF-8?q?=D1=86=D0=B2=D0=B5=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/FigmaGen/Commands/TokensCommand.swift | 29 ++++++++ Sources/FigmaGen/Dependencies.swift | 12 +++- .../Tokens/Contexts/BaseColorToken.swift | 9 +++ .../Tokens/DefaultTokensGenerator.swift | 15 +++- ...ltTokensGenerationParametersResolver.swift | 41 ++++++++--- .../BaseColor/BaseColorTokensGenerator.swift | 8 +++ .../DefaultBaseColorTokensGenerator.swift | 41 +++++++++++ .../Color/DefaultColorTokensGenerator.swift | 2 +- .../Resolver/DefaultTokensResolver.swift | 71 ++++++++++++++----- .../Tokens/TokensTemplateConfiguration.swift | 1 + .../TokensGenerationParameters.swift | 1 + ...ilCollectionRemovingFirstModificator.swift | 24 +++++++ .../Hex/StencilFullHexFilter.swift | 44 ++++++++++++ .../StencilHexToAlphaFilter.swift | 0 ...angeReplaceableCollection+Extensions.swift | 21 +++--- Templates/BaseColorTokens.stencil | 39 ++++++++++ Templates/ColorTokens.stencil | 3 +- Tests/FigmaGenTests/TokensResolverTests.swift | 16 +++-- 18 files changed, 329 insertions(+), 48 deletions(-) create mode 100644 Sources/FigmaGen/Generators/Tokens/Contexts/BaseColorToken.swift create mode 100644 Sources/FigmaGen/Generators/Tokens/Generators/BaseColor/BaseColorTokensGenerator.swift create mode 100644 Sources/FigmaGen/Generators/Tokens/Generators/BaseColor/DefaultBaseColorTokensGenerator.swift create mode 100644 Sources/FigmaGen/Render/StencilExtensions/Collection/StencilCollectionRemovingFirstModificator.swift create mode 100644 Sources/FigmaGen/Render/StencilExtensions/Hex/StencilFullHexFilter.swift rename Sources/FigmaGen/Render/StencilExtensions/{String => Hex}/StencilHexToAlphaFilter.swift (100%) create mode 100644 Templates/BaseColorTokens.stencil diff --git a/Sources/FigmaGen/Commands/TokensCommand.swift b/Sources/FigmaGen/Commands/TokensCommand.swift index 4ab99e1..30ddd38 100644 --- a/Sources/FigmaGen/Commands/TokensCommand.swift +++ b/Sources/FigmaGen/Commands/TokensCommand.swift @@ -57,6 +57,30 @@ final class TokensCommand: AsyncExecutableCommand { """ ) + let baseColorsTemplate = Key( + "--base-colors-template", + description: """ + Path to the template file. + If no template is passed a default template will be used. + """ + ) + + let baseColorsTemplateOptions = VariadicKey( + "--base-colors-options", + description: #""" + An option that will be merged with template context, and overwrite any values of the same name. + Can be repeated multiple times and must be in the format: -o "name:value". + """# + ) + + let baseColorsDestination = Key( + "--base-colors-destination", + description: """ + The path to the file to generate. + By default, generated code will be printed on stdout. + """ + ) + // MARK: - Initializers init(generator: TokensGenerator) { @@ -88,6 +112,11 @@ extension TokensCommand { template: colorsTemplate.value, templateOptions: resolveTemplateOptions(colorsTemplateOptions.value), destination: colorsDestination.value + ), + baseColor: TokensTemplateConfiguration.Template( + template: baseColorsTemplate.value, + templateOptions: resolveTemplateOptions(baseColorsTemplateOptions.value), + destination: baseColorsDestination.value ) ) ) diff --git a/Sources/FigmaGen/Dependencies.swift b/Sources/FigmaGen/Dependencies.swift index 28c332c..da51ec1 100644 --- a/Sources/FigmaGen/Dependencies.swift +++ b/Sources/FigmaGen/Dependencies.swift @@ -87,7 +87,9 @@ enum Dependencies { StencilFontSystemFilter(contextCoder: templateContextCoder), StencilCollectionDropFirstModificator(), StencilCollectionDropLastModificator(), - StencilHexToAlphaFilter() + StencilCollectionRemovingFirstModificator(), + StencilHexToAlphaFilter(), + StencilFullHexFilter() ] static let templateRenderer: TemplateRenderer = DefaultTemplateRenderer( @@ -130,9 +132,15 @@ enum Dependencies { templateRenderer: templateRenderer ) + static let baseColorTokensGenerator: BaseColorTokensGenerator = DefaultBaseColorTokensGenerator( + tokensResolver: tokensResolver, + templateRenderer: templateRenderer + ) + static let tokensGenerator: TokensGenerator = DefaultTokensGenerator( tokensProvider: tokensProvider, tokensGenerationParametersResolver: tokensGenerationParametersResolver, - colorTokensGenerator: colorTokensGenerator + colorTokensGenerator: colorTokensGenerator, + baseColorTokensGenerator: baseColorTokensGenerator ) } diff --git a/Sources/FigmaGen/Generators/Tokens/Contexts/BaseColorToken.swift b/Sources/FigmaGen/Generators/Tokens/Contexts/BaseColorToken.swift new file mode 100644 index 0000000..1091848 --- /dev/null +++ b/Sources/FigmaGen/Generators/Tokens/Contexts/BaseColorToken.swift @@ -0,0 +1,9 @@ +import Foundation + +struct BaseColorToken: Encodable { + + // MARK: - Instance Properties + + let path: [String] + let value: String +} diff --git a/Sources/FigmaGen/Generators/Tokens/DefaultTokensGenerator.swift b/Sources/FigmaGen/Generators/Tokens/DefaultTokensGenerator.swift index 87bdcc3..43572dc 100644 --- a/Sources/FigmaGen/Generators/Tokens/DefaultTokensGenerator.swift +++ b/Sources/FigmaGen/Generators/Tokens/DefaultTokensGenerator.swift @@ -9,17 +9,20 @@ final class DefaultTokensGenerator: TokensGenerator { let tokensProvider: TokensProvider let tokensGenerationParametersResolver: TokensGenerationParametersResolver let colorTokensGenerator: ColorTokensGenerator + let baseColorTokensGenerator: BaseColorTokensGenerator // MARK: - Initializers init( tokensProvider: TokensProvider, tokensGenerationParametersResolver: TokensGenerationParametersResolver, - colorTokensGenerator: ColorTokensGenerator + colorTokensGenerator: ColorTokensGenerator, + baseColorTokensGenerator: BaseColorTokensGenerator ) { self.tokensProvider = tokensProvider self.tokensGenerationParametersResolver = tokensGenerationParametersResolver self.colorTokensGenerator = colorTokensGenerator + self.baseColorTokensGenerator = baseColorTokensGenerator } // MARK: - Instance Methods @@ -27,7 +30,15 @@ final class DefaultTokensGenerator: TokensGenerator { private func generate(parameters: TokensGenerationParameters) async throws { let tokenValues = try await tokensProvider.fetchTokens(from: parameters.file) - try colorTokensGenerator.generate(renderParameters: parameters.tokens.colorRender, tokenValues: tokenValues) + try colorTokensGenerator.generate( + renderParameters: parameters.tokens.colorRender, + tokenValues: tokenValues + ) + + try baseColorTokensGenerator.generate( + renderParameters: parameters.tokens.baseColorRender, + tokenValues: tokenValues + ) } // MARK: - diff --git a/Sources/FigmaGen/Generators/Tokens/GenerationParametersResolver/DefaultTokensGenerationParametersResolver.swift b/Sources/FigmaGen/Generators/Tokens/GenerationParametersResolver/DefaultTokensGenerationParametersResolver.swift index 83bfde2..3554f6e 100644 --- a/Sources/FigmaGen/Generators/Tokens/GenerationParametersResolver/DefaultTokensGenerationParametersResolver.swift +++ b/Sources/FigmaGen/Generators/Tokens/GenerationParametersResolver/DefaultTokensGenerationParametersResolver.swift @@ -17,29 +17,39 @@ final class DefaultTokensGenerationParametersResolver: TokensGenerationParameter } } - private func resolveColorTemplateType(configuration: TokensConfiguration) -> RenderTemplateType { - if let templatePath = configuration.templates?.color?.template { + private func resolveTemplateType( + template: TokensTemplateConfiguration.Template?, + nativeTemplateName: String + ) -> RenderTemplateType { + if let templatePath = template?.template { return .custom(path: templatePath) } - return .native(name: "ColorTokens") + return .native(name: nativeTemplateName) } - private func resolveColorDestination(configuration: TokensConfiguration) -> RenderDestination { - if let destinationPath = configuration.templates?.color?.destination { + private func resolveDestination(template: TokensTemplateConfiguration.Template?) -> RenderDestination { + if let destinationPath = template?.destination { return .file(path: destinationPath) } return .console } - private func resolveColorRenderParameters(configuration: TokensConfiguration) -> RenderParameters { - let templateType = resolveColorTemplateType(configuration: configuration) - let destination = resolveColorDestination(configuration: configuration) + private func resolveRenderParameters( + template: TokensTemplateConfiguration.Template?, + nativeTemplateName: String + ) -> RenderParameters { + let templateType = resolveTemplateType( + template: template, + nativeTemplateName: nativeTemplateName + ) + + let destination = resolveDestination(template: template) let template = RenderTemplate( type: templateType, - options: configuration.templates?.color?.templateOptions ?? [:] + options: template?.templateOptions ?? [:] ) return RenderParameters(template: template, destination: destination) @@ -62,12 +72,21 @@ final class DefaultTokensGenerationParametersResolver: TokensGenerationParameter accessToken: accessToken ) - let colorRender = resolveColorRenderParameters(configuration: configuration) + let colorRender = resolveRenderParameters( + template: configuration.templates?.color, + nativeTemplateName: "ColorTokens" + ) + + let baseColorRender = resolveRenderParameters( + template: configuration.templates?.baseColor, + nativeTemplateName: "BaseColorTokens" + ) return TokensGenerationParameters( file: file, tokens: TokensGenerationParameters.TokensParameters( - colorRender: colorRender + colorRender: colorRender, + baseColorRender: baseColorRender ) ) } diff --git a/Sources/FigmaGen/Generators/Tokens/Generators/BaseColor/BaseColorTokensGenerator.swift b/Sources/FigmaGen/Generators/Tokens/Generators/BaseColor/BaseColorTokensGenerator.swift new file mode 100644 index 0000000..d87f17d --- /dev/null +++ b/Sources/FigmaGen/Generators/Tokens/Generators/BaseColor/BaseColorTokensGenerator.swift @@ -0,0 +1,8 @@ +import Foundation + +protocol BaseColorTokensGenerator { + + // MARK: - Instance Methods + + func generate(renderParameters: RenderParameters, tokenValues: TokenValues) throws +} diff --git a/Sources/FigmaGen/Generators/Tokens/Generators/BaseColor/DefaultBaseColorTokensGenerator.swift b/Sources/FigmaGen/Generators/Tokens/Generators/BaseColor/DefaultBaseColorTokensGenerator.swift new file mode 100644 index 0000000..1ff067e --- /dev/null +++ b/Sources/FigmaGen/Generators/Tokens/Generators/BaseColor/DefaultBaseColorTokensGenerator.swift @@ -0,0 +1,41 @@ +import Foundation + +final class DefaultBaseColorTokensGenerator: BaseColorTokensGenerator { + + // MARK: - Instance Properties + + let tokensResolver: TokensResolver + let templateRenderer: TemplateRenderer + + // MARK: - Initializers + + init(tokensResolver: TokensResolver, templateRenderer: TemplateRenderer) { + self.tokensResolver = tokensResolver + self.templateRenderer = templateRenderer + } + + // MARK: - Instance Methods + + private func makeBaseColorToken(from token: TokenValue, tokenValues: TokenValues) throws -> BaseColorToken? { + guard case .color(let value) = token.type else { + return nil + } + + return BaseColorToken( + path: token.name.components(separatedBy: "."), + value: try tokensResolver.resolveHexColorValue(value, tokenValues: tokenValues) + ) + } + + // MARK: - + + func generate(renderParameters: RenderParameters, tokenValues: TokenValues) throws { + let colors = try tokenValues.colors.compactMap { try makeBaseColorToken(from: $0, tokenValues: tokenValues) } + + try templateRenderer.renderTemplate( + renderParameters.template, + to: renderParameters.destination, + context: ["colors": colors] + ) + } +} diff --git a/Sources/FigmaGen/Generators/Tokens/Generators/Color/DefaultColorTokensGenerator.swift b/Sources/FigmaGen/Generators/Tokens/Generators/Color/DefaultColorTokensGenerator.swift index 35780c3..388cce1 100644 --- a/Sources/FigmaGen/Generators/Tokens/Generators/Color/DefaultColorTokensGenerator.swift +++ b/Sources/FigmaGen/Generators/Tokens/Generators/Color/DefaultColorTokensGenerator.swift @@ -107,7 +107,7 @@ final class DefaultColorTokensGenerator: ColorTokensGenerator { ) ), name: tokenName, - path: path.removingFirst() + path: Array(path.dropFirst()) ) } diff --git a/Sources/FigmaGen/Generators/Tokens/Resolver/DefaultTokensResolver.swift b/Sources/FigmaGen/Generators/Tokens/Resolver/DefaultTokensResolver.swift index 8f0aeff..6e5d476 100644 --- a/Sources/FigmaGen/Generators/Tokens/Resolver/DefaultTokensResolver.swift +++ b/Sources/FigmaGen/Generators/Tokens/Resolver/DefaultTokensResolver.swift @@ -15,25 +15,50 @@ final class DefaultTokensResolver: TokensResolver { } } + private func colorComponent(from string: String, start: Int, length: Int) -> Double { + let startIndex = string.index(string.startIndex, offsetBy: start) + let endIndex = string.index(startIndex, offsetBy: length) + let substring = string[startIndex.. Color { let hex = hex + .replacingOccurrences(of: "#", with: "") .trimmingCharacters(in: .whitespacesAndNewlines) .uppercased() - .filter { $0 != "#" } - guard hex.count == 6 else { + switch hex.count { + case .rgb: + return Color( + red: colorComponent(from: hex, start: 0, length: 1), + green: colorComponent(from: hex, start: 1, length: 1), + blue: colorComponent(from: hex, start: 2, length: 1), + alpha: alpha + ) + + case .rrggbb: + return Color( + red: colorComponent(from: hex, start: 0, length: 2), + green: colorComponent(from: hex, start: 2, length: 2), + blue: colorComponent(from: hex, start: 4, length: 2), + alpha: alpha + ) + + default: throw TokensGeneratorError(code: .invalidHEXComponent(hex: hex)) } - - var rgbValue: UInt64 = 0 - Scanner(string: hex).scanHexInt64(&rgbValue) - - return Color( - red: Double((rgbValue & 0xFF0000) >> 16) / 255.0, - green: Double((rgbValue & 0x00FF00) >> 8) / 255.0, - blue: Double(rgbValue & 0x0000FF) / 255.0, - alpha: alpha - ) } private func resolveColorValue(_ value: String, tokenValues: TokenValues) throws -> Color { @@ -50,9 +75,11 @@ final class DefaultTokensResolver: TokensResolver { let allTokens = tokenValues.all let resolvedValue = try value.replacingOccurrences(matchingPattern: #"\{.*?\}"#) { referenceName in - let referenceName = referenceName - .removingFirst() - .removingLast() + let referenceName = String( + referenceName + .dropFirst() + .dropLast() + ) guard let token = allTokens.first(where: { $0.name == referenceName }) else { throw TokensGeneratorError(code: .referenceNotFound(name: referenceName)) @@ -90,6 +117,10 @@ final class DefaultTokensResolver: TokensResolver { func resolveHexColorValue(_ value: String, tokenValues: TokenValues) throws -> String { let resolvedValue = try resolveValue(value, tokenValues: tokenValues) + if resolvedValue.hasPrefix("#") { + return value + } + return try resolveColorValue(resolvedValue, tokenValues: tokenValues).hexString } @@ -110,7 +141,7 @@ final class DefaultTokensResolver: TokensResolver { let angle = params[0] let colorStopList = try params - .removingFirst() + .dropFirst() .map { rawColorStop in guard let separatorRange = rawColorStop.range(of: " ", options: .backwards) else { throw TokensGeneratorError(code: .failedToExtractLinearGradientParams(linearGradient: value)) @@ -129,3 +160,11 @@ final class DefaultTokensResolver: TokensResolver { ) } } + +extension Int { + + // MARK: - Type Properties + + fileprivate static let rgb = 3 + fileprivate static let rrggbb = 6 +} diff --git a/Sources/FigmaGen/Models/Configuration/Tokens/TokensTemplateConfiguration.swift b/Sources/FigmaGen/Models/Configuration/Tokens/TokensTemplateConfiguration.swift index 8f8c686..4995ade 100644 --- a/Sources/FigmaGen/Models/Configuration/Tokens/TokensTemplateConfiguration.swift +++ b/Sources/FigmaGen/Models/Configuration/Tokens/TokensTemplateConfiguration.swift @@ -29,6 +29,7 @@ struct TokensTemplateConfiguration: Decodable { // MARK: - Instance Properties let color: Template? + let baseColor: Template? } extension TokensTemplateConfiguration.Template { diff --git a/Sources/FigmaGen/Models/Parameters/TokensGenerationParameters.swift b/Sources/FigmaGen/Models/Parameters/TokensGenerationParameters.swift index 8fb38aa..1999fac 100644 --- a/Sources/FigmaGen/Models/Parameters/TokensGenerationParameters.swift +++ b/Sources/FigmaGen/Models/Parameters/TokensGenerationParameters.swift @@ -9,6 +9,7 @@ struct TokensGenerationParameters { // MARK: - Instance Properties let colorRender: RenderParameters + let baseColorRender: RenderParameters } // MARK: - Instance Properties diff --git a/Sources/FigmaGen/Render/StencilExtensions/Collection/StencilCollectionRemovingFirstModificator.swift b/Sources/FigmaGen/Render/StencilExtensions/Collection/StencilCollectionRemovingFirstModificator.swift new file mode 100644 index 0000000..a62955a --- /dev/null +++ b/Sources/FigmaGen/Render/StencilExtensions/Collection/StencilCollectionRemovingFirstModificator.swift @@ -0,0 +1,24 @@ +import Foundation + +final class StencilCollectionRemovingFirstModificator: StencilModificator { + + // MARK: - Instance Properties + + let name = "removingFirst" + + // MARK: - Instance Methods + + func modify(input: Any, withArguments arguments: [Any?]) throws -> Any { + guard let element = arguments.first as? String else { + throw StencilModificatorError(code: .invalidArguments(arguments), filter: name) + } + + guard let array = input as? [Any?] else { + throw StencilModificatorError(code: .invalidValue(input), filter: name) + } + + return array + .map { stringify($0) } + .removingFirst(element) + } +} diff --git a/Sources/FigmaGen/Render/StencilExtensions/Hex/StencilFullHexFilter.swift b/Sources/FigmaGen/Render/StencilExtensions/Hex/StencilFullHexFilter.swift new file mode 100644 index 0000000..07cd407 --- /dev/null +++ b/Sources/FigmaGen/Render/StencilExtensions/Hex/StencilFullHexFilter.swift @@ -0,0 +1,44 @@ +import Foundation + +final class StencilFullHexFilter: StencilFilter { + + // MARK: - Instance Properties + + let name = "fullHex" + + // MARK: - Instance Methods + + func filter(input: String) throws -> String { + guard input.hasPrefix("#") else { + throw StencilFilterError(code: .invalidValue(input), filter: name) + } + + let hex = input.uppercased() + + switch hex.count { + case .rgb: + return Array(hex) + .map { "\($0)\($0)" } + .joined() + .appending("FF") + + case .rrggbb: + return hex.appending("FF") + + case .rrggbbaa: + return hex + + default: + throw StencilFilterError(code: .invalidValue(input), filter: name) + } + } +} + +extension Int { + + // MARK: - Type Properties + + fileprivate static let rgb = 4 + fileprivate static let rrggbb = 7 + fileprivate static let rrggbbaa = 9 +} diff --git a/Sources/FigmaGen/Render/StencilExtensions/String/StencilHexToAlphaFilter.swift b/Sources/FigmaGen/Render/StencilExtensions/Hex/StencilHexToAlphaFilter.swift similarity index 100% rename from Sources/FigmaGen/Render/StencilExtensions/String/StencilHexToAlphaFilter.swift rename to Sources/FigmaGen/Render/StencilExtensions/Hex/StencilHexToAlphaFilter.swift diff --git a/Sources/FigmaGenTools/Extensions/RangeReplaceableCollection+Extensions.swift b/Sources/FigmaGenTools/Extensions/RangeReplaceableCollection+Extensions.swift index e393646..7e687fd 100644 --- a/Sources/FigmaGenTools/Extensions/RangeReplaceableCollection+Extensions.swift +++ b/Sources/FigmaGenTools/Extensions/RangeReplaceableCollection+Extensions.swift @@ -27,21 +27,24 @@ extension RangeReplaceableCollection { public func appending(_ element: Element) -> Self { appending(contentsOf: [element]) } - - public func removingFirst() -> Self { - var copy = self - copy.removeFirst() - return copy - } } -extension RangeReplaceableCollection where Self: BidirectionalCollection { +extension RangeReplaceableCollection where Element: Equatable { // MARK: - Instance Methods - public func removingLast() -> Self { + @discardableResult + public mutating func removeFirst(_ element: Element) -> Element? { + guard let index = firstIndex(of: element) else { + return nil + } + + return remove(at: index) + } + + public func removingFirst(_ element: Element) -> Self { var copy = self - copy.removeLast() + copy.removeFirst(element) return copy } } diff --git a/Templates/BaseColorTokens.stencil b/Templates/BaseColorTokens.stencil new file mode 100644 index 0000000..8f3f249 --- /dev/null +++ b/Templates/BaseColorTokens.stencil @@ -0,0 +1,39 @@ +{% include "FileHeader.stencil" %} +{% if colors %} +{% set accessModifier %}{% if options.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set tokenTypeName %}{{ options.tokenTypeName|default:"BaseColorTokens" }}{% endset %} +{% set colorTypeName %}{{ options.colorTypeName|default:"UIColor" }}{% endset %} +{% macro propertyName name %}{{ name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords }}{% endmacro %} + +#if canImport(UIKit) +import UIKit +#else +import AppKit +#endif + +{{ accessModifier }} struct {{ tokenTypeName }} { + {% for color in colors %} + /// {{ color.value }} + {{ accessModifier }} let {% call propertyName color.path|removingFirst:"color"|removingFirst:"base"|join:"." %} = {{ colorTypeName }}(hex: 0x{{ color.value|replace:"#",""|uppercase }}) + {% endfor %} +} +{% else %} +// No base color tokens found +{% endif %} + +private extension {{ colorTypeName }} { + + convenience init(hex: UInt32) { + let red = UInt8((hex >> 24) & 0xFF) + let green = UInt8((hex >> 16) & 0xFF) + let blue = UInt8((hex >> 8) & 0xFF) + let alpha = UInt8(hex & 0xFF) + + self.init( + red: CGFloat(red) / 255.0, + green: CGFloat(green) / 255.0, + blue: CGFloat(blue) / 255.0, + alpha: CGFloat(alpha) / 255.0 + ) + } +} \ No newline at end of file diff --git a/Templates/ColorTokens.stencil b/Templates/ColorTokens.stencil index 5ce9665..6213e44 100644 --- a/Templates/ColorTokens.stencil +++ b/Templates/ColorTokens.stencil @@ -1,5 +1,4 @@ -// swiftlint:disable all -// Generated using FigmaGen - https://github.com/hhru/FigmaGen +{% include "FileHeader.stencil" %} {% set colorTypeName %}{{ options.colorTypeName|default:"UIColor" }}{% endset %} {% set accessModifier %}{% if options.publicAccess %}public{% else %}internal{% endif %}{% endset %} {% set tokenTypeName %}{{ options.tokenTypeName|default:"ColorTokens" }}{% endset %} diff --git a/Tests/FigmaGenTests/TokensResolverTests.swift b/Tests/FigmaGenTests/TokensResolverTests.swift index 287768c..b168804 100644 --- a/Tests/FigmaGenTests/TokensResolverTests.swift +++ b/Tests/FigmaGenTests/TokensResolverTests.swift @@ -181,19 +181,25 @@ final class TokensResolverTests: XCTestCase { TokenValue(type: .opacity(value: "{core.opacity.48}"), name: "semantic.opacity.disabled") ], colors: [ - TokenValue(type: .core(value: "#ffffff"), name: "color.base.white") + TokenValue(type: .color(value: "#ffffff"), name: "color.base.white"), + TokenValue(type: .color(value: "#111"), name: "color.base.gray.5") ], typography: [], day: [], night: [] ) - let value = "rgba({color.base.white}, {semantic.opacity.disabled})" - let expectedHexColor = "#FFFFFF7A" + let value1 = "rgba({color.base.white}, {semantic.opacity.disabled})" + let expectedHexColor1 = "#FFFFFF7A" - let actualHexColor = try tokensResolver.resolveHexColorValue(value, tokenValues: tokenValues) + let value2 = "rgba( {color.base.gray.5} , {semantic.opacity.disabled})" + let expectedHexColor2 = "#1111117A" - XCTAssertEqual(actualHexColor, expectedHexColor) + let actualHexColor1 = try tokensResolver.resolveHexColorValue(value1, tokenValues: tokenValues) + let actualHexColor2 = try tokensResolver.resolveHexColorValue(value2, tokenValues: tokenValues) + + XCTAssertEqual(actualHexColor1, expectedHexColor1) + XCTAssertEqual(actualHexColor2, expectedHexColor2) } func testResolveHexColorWithoutReferences() throws { From a4395d06bf68de217407fbb16e0b92cca76768af Mon Sep 17 00:00:00 2001 From: Timur Shafigullin Date: Wed, 19 Jul 2023 12:57:14 +0300 Subject: [PATCH 4/5] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20hex=20alpha=20=D0=B2=20=D0=BD=D0=B0=D1=87?= =?UTF-8?q?=D0=B0=D0=BB=D0=BE=20=D1=86=D0=B2=D0=B5=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Hex/StencilFullHexFilter.swift | 44 ----------- .../Hex/StencilFullHexModificator.swift | 76 +++++++++++++++++++ 2 files changed, 76 insertions(+), 44 deletions(-) delete mode 100644 Sources/FigmaGen/Render/StencilExtensions/Hex/StencilFullHexFilter.swift create mode 100644 Sources/FigmaGen/Render/StencilExtensions/Hex/StencilFullHexModificator.swift diff --git a/Sources/FigmaGen/Render/StencilExtensions/Hex/StencilFullHexFilter.swift b/Sources/FigmaGen/Render/StencilExtensions/Hex/StencilFullHexFilter.swift deleted file mode 100644 index 07cd407..0000000 --- a/Sources/FigmaGen/Render/StencilExtensions/Hex/StencilFullHexFilter.swift +++ /dev/null @@ -1,44 +0,0 @@ -import Foundation - -final class StencilFullHexFilter: StencilFilter { - - // MARK: - Instance Properties - - let name = "fullHex" - - // MARK: - Instance Methods - - func filter(input: String) throws -> String { - guard input.hasPrefix("#") else { - throw StencilFilterError(code: .invalidValue(input), filter: name) - } - - let hex = input.uppercased() - - switch hex.count { - case .rgb: - return Array(hex) - .map { "\($0)\($0)" } - .joined() - .appending("FF") - - case .rrggbb: - return hex.appending("FF") - - case .rrggbbaa: - return hex - - default: - throw StencilFilterError(code: .invalidValue(input), filter: name) - } - } -} - -extension Int { - - // MARK: - Type Properties - - fileprivate static let rgb = 4 - fileprivate static let rrggbb = 7 - fileprivate static let rrggbbaa = 9 -} diff --git a/Sources/FigmaGen/Render/StencilExtensions/Hex/StencilFullHexModificator.swift b/Sources/FigmaGen/Render/StencilExtensions/Hex/StencilFullHexModificator.swift new file mode 100644 index 0000000..5ce4fe8 --- /dev/null +++ b/Sources/FigmaGen/Render/StencilExtensions/Hex/StencilFullHexModificator.swift @@ -0,0 +1,76 @@ +import Foundation + +final class StencilFullHexModificator: StencilModificator { + + // MARK: - Nested Types + + fileprivate enum AlphaHexPosition: String { + + // MARK: - Enumeration Cases + + case start + case end + } + + // MARK: - Instance Properties + + let name = "fullHex" + + // MARK: - Instance Methods + + func modify(input: String, withArguments arguments: [Any?]) throws -> String { + let hexPosition = arguments + .first + .flatMap { $0 as? String } + .flatMap { AlphaHexPosition(rawValue: $0) } ?? .end + + guard input.hasPrefix("#") else { + throw StencilFilterError(code: .invalidValue(input), filter: name) + } + + let hex = input.uppercased() + + switch hex.count { + case .rgb: + return Array(hex) + .map { "\($0)\($0)" } + .joined() + .appendingHexAlpha(to: hexPosition) + + case .rrggbb: + return hex.appendingHexAlpha(to: hexPosition) + + case .rrggbbaa: + return hex + + default: + throw StencilFilterError(code: .invalidValue(input), filter: name) + } + } +} + +extension Int { + + // MARK: - Type Properties + + fileprivate static let rgb = 4 + fileprivate static let rrggbb = 7 + fileprivate static let rrggbbaa = 9 +} + +extension String { + + // MARK: - Instance Methods + + fileprivate func appendingHexAlpha(to position: StencilFullHexModificator.AlphaHexPosition) -> Self { + let alphaHex = "FF" + + switch position { + case .start: + return alphaHex + self + + case .end: + return self + alphaHex + } + } +} From 9c0cf1fb85eb7c7db1a5bb394a3aae2ef665abbe Mon Sep 17 00:00:00 2001 From: Timur Shafigullin Date: Wed, 19 Jul 2023 16:39:02 +0300 Subject: [PATCH 5/5] =?UTF-8?q?=D0=93=D0=B5=D0=BD=D0=B5=D1=80=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20=D1=82=D0=BE=D0=BA=D0=B5=D0=BD=D0=BE=D0=B2=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D0=B4=D0=B5=D0=BC=D0=BE=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=D0=BA=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Demo/.figmagen.yml | 12 + Demo/FigmaGenDemo.xcodeproj/project.pbxproj | 12 +- .../Generated/BaseColorTokens.swift | 494 ++++++++++++++++++ Demo/FigmaGenDemo/Generated/ColorTokens.swift | 77 +++ Sources/FigmaGen/Commands/TokensCommand.swift | 4 +- Sources/FigmaGen/Dependencies.swift | 19 +- .../Library/DefaultLibraryGenerator.swift | 9 +- ...ltTokensGenerationParametersResolver.swift | 4 +- .../Color/DefaultColorTokensGenerator.swift | 4 +- .../Resolver/DefaultTokensResolver.swift | 5 +- .../Models/Configuration/Configuration.swift | 5 + .../Tokens/TokensConfiguration.swift | 14 + .../Tokens/TokensTemplateConfiguration.swift | 4 +- .../Models/Token/TokensStudioPluginData.swift | 1 - .../Extensions/Promise+Extensions.swift | 15 + Templates/BaseColorTokens.stencil | 10 +- Templates/ColorTokens.stencil | 16 +- 17 files changed, 667 insertions(+), 38 deletions(-) create mode 100644 Demo/FigmaGenDemo/Generated/BaseColorTokens.swift create mode 100644 Demo/FigmaGenDemo/Generated/ColorTokens.swift diff --git a/Demo/.figmagen.yml b/Demo/.figmagen.yml index c2aba8e..e5eddc8 100644 --- a/Demo/.figmagen.yml +++ b/Demo/.figmagen.yml @@ -30,3 +30,15 @@ shadowStyles: destination: FigmaGenDemo/Generated/ShadowStyle.swift templateOptions: publicAccess: true + +tokens: + file: https://www.figma.com/file/W6dy4CFSZWUVpVnSzNZIxA/FigmaGen-Tokens-Demo + templates: + colors: + destination: FigmaGenDemo/Generated/ColorTokens.swift + templateOptions: + publicAccess: true + baseColors: + destination: FigmaGenDemo/Generated/BaseColorTokens.swift + templateOptions: + publicAccess: true diff --git a/Demo/FigmaGenDemo.xcodeproj/project.pbxproj b/Demo/FigmaGenDemo.xcodeproj/project.pbxproj index d04624b..d6c74e9 100644 --- a/Demo/FigmaGenDemo.xcodeproj/project.pbxproj +++ b/Demo/FigmaGenDemo.xcodeproj/project.pbxproj @@ -22,6 +22,8 @@ /* Begin PBXBuildFile section */ 7ED1B6F1C418840A5C1245BE /* Pods_FigmaGenDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 11B7DAD9DD79D94016B46F17 /* Pods_FigmaGenDemo.framework */; }; + 8E44BFD42A67ECB300EE5D7E /* BaseColorTokens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E44BFD22A67ECB300EE5D7E /* BaseColorTokens.swift */; }; + 8E44BFD52A67ECB300EE5D7E /* ColorTokens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E44BFD32A67ECB300EE5D7E /* ColorTokens.swift */; }; 8E5334092A420D9B006D6569 /* ShadowStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E5333F42A420D9B006D6569 /* ShadowStyle.swift */; }; 8E53340A2A420D9B006D6569 /* TextStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E5333F52A420D9B006D6569 /* TextStyle.swift */; }; 8E53340B2A420D9B006D6569 /* Images.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E5333F62A420D9B006D6569 /* Images.swift */; }; @@ -54,6 +56,8 @@ 11B7DAD9DD79D94016B46F17 /* Pods_FigmaGenDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FigmaGenDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 23C6A1263942D9DA0D5FFDCC /* Pods-FigmaGenDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FigmaGenDemo.release.xcconfig"; path = "Target Support Files/Pods-FigmaGenDemo/Pods-FigmaGenDemo.release.xcconfig"; sourceTree = ""; }; 29B6BD966DD89C853A1AFFD2 /* Pods-FugenDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FugenDemo.debug.xcconfig"; path = "Target Support Files/Pods-FugenDemo/Pods-FugenDemo.debug.xcconfig"; sourceTree = ""; }; + 8E44BFD22A67ECB300EE5D7E /* BaseColorTokens.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseColorTokens.swift; sourceTree = ""; }; + 8E44BFD32A67ECB300EE5D7E /* ColorTokens.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorTokens.swift; sourceTree = ""; }; 8E5333F42A420D9B006D6569 /* ShadowStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShadowStyle.swift; sourceTree = ""; }; 8E5333F52A420D9B006D6569 /* TextStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextStyle.swift; sourceTree = ""; }; 8E5333F62A420D9B006D6569 /* Images.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Images.swift; sourceTree = ""; }; @@ -128,10 +132,12 @@ 8E5333F32A420D9B006D6569 /* Generated */ = { isa = PBXGroup; children = ( + 8E44BFD22A67ECB300EE5D7E /* BaseColorTokens.swift */, + 8E5333F72A420D9B006D6569 /* ColorStyle.swift */, + 8E44BFD32A67ECB300EE5D7E /* ColorTokens.swift */, + 8E5333F62A420D9B006D6569 /* Images.swift */, 8E5333F42A420D9B006D6569 /* ShadowStyle.swift */, 8E5333F52A420D9B006D6569 /* TextStyle.swift */, - 8E5333F62A420D9B006D6569 /* Images.swift */, - 8E5333F72A420D9B006D6569 /* ColorStyle.swift */, ); path = Generated; sourceTree = ""; @@ -367,12 +373,14 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 8E44BFD42A67ECB300EE5D7E /* BaseColorTokens.swift in Sources */, 8E5334172A420D9B006D6569 /* AppDelegate.swift in Sources */, 8E53340F2A420D9B006D6569 /* ViewController.swift in Sources */, 8E53340A2A420D9B006D6569 /* TextStyle.swift in Sources */, 8E53340B2A420D9B006D6569 /* Images.swift in Sources */, 8E5334092A420D9B006D6569 /* ShadowStyle.swift in Sources */, 8E53340C2A420D9B006D6569 /* ColorStyle.swift in Sources */, + 8E44BFD52A67ECB300EE5D7E /* ColorTokens.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Demo/FigmaGenDemo/Generated/BaseColorTokens.swift b/Demo/FigmaGenDemo/Generated/BaseColorTokens.swift new file mode 100644 index 0000000..add81a6 --- /dev/null +++ b/Demo/FigmaGenDemo/Generated/BaseColorTokens.swift @@ -0,0 +1,494 @@ +// swiftlint:disable all +// Generated using FigmaGen - https://github.com/hhru/FigmaGen + +#if canImport(UIKit) +import UIKit +#else +import AppKit +#endif + +public struct BaseColorTokens { + /// #ffffff + public let colorsDefaultWhite = UIColor(hex: 0xFFFFFFFF) + /// #000000 + public let colorsDefaultBlack = UIColor(hex: 0x000000FF) + /// #00000000 + public let colorsDefaultTransparent = UIColor(hex: 0x00000000) + /// #f9fafb + public let colorsDefaultGray50 = UIColor(hex: 0xF9FAFBFF) + /// #f7fafc + public let colorsDefaultGray100 = UIColor(hex: 0xF7FAFCFF) + /// #edf2f7 + public let colorsDefaultGray200 = UIColor(hex: 0xEDF2F7FF) + /// #e2e8f0 + public let colorsDefaultGray300 = UIColor(hex: 0xE2E8F0FF) + /// #cbd5e0 + public let colorsDefaultGray400 = UIColor(hex: 0xCBD5E0FF) + /// #a0aec0 + public let colorsDefaultGray500 = UIColor(hex: 0xA0AEC0FF) + /// #718096 + public let colorsDefaultGray600 = UIColor(hex: 0x718096FF) + /// #4a5568 + public let colorsDefaultGray700 = UIColor(hex: 0x4A5568FF) + /// #2d3748 + public let colorsDefaultGray800 = UIColor(hex: 0x2D3748FF) + /// #1a202c + public let colorsDefaultGray900 = UIColor(hex: 0x1A202CFF) + /// #f8fafc + public let colorsSlate50 = UIColor(hex: 0xF8FAFCFF) + /// #f1f5f9 + public let colorsSlate100 = UIColor(hex: 0xF1F5F9FF) + /// #e2e8f0 + public let colorsSlate200 = UIColor(hex: 0xE2E8F0FF) + /// #cbd5e1 + public let colorsSlate300 = UIColor(hex: 0xCBD5E1FF) + /// #94a3b8 + public let colorsSlate400 = UIColor(hex: 0x94A3B8FF) + /// #64748b + public let colorsSlate500 = UIColor(hex: 0x64748BFF) + /// #475569 + public let colorsSlate600 = UIColor(hex: 0x475569FF) + /// #334155 + public let colorsSlate700 = UIColor(hex: 0x334155FF) + /// #1e293b + public let colorsSlate800 = UIColor(hex: 0x1E293BFF) + /// #0f172a + public let colorsSlate900 = UIColor(hex: 0x0F172AFF) + /// #f9fafb + public let colorsGray50 = UIColor(hex: 0xF9FAFBFF) + /// #f3f4f6 + public let colorsGray100 = UIColor(hex: 0xF3F4F6FF) + /// #e5e7eb + public let colorsGray200 = UIColor(hex: 0xE5E7EBFF) + /// #d1d5db + public let colorsGray300 = UIColor(hex: 0xD1D5DBFF) + /// #9ca3af + public let colorsGray400 = UIColor(hex: 0x9CA3AFFF) + /// #6b7280 + public let colorsGray500 = UIColor(hex: 0x6B7280FF) + /// #4b5563 + public let colorsGray600 = UIColor(hex: 0x4B5563FF) + /// #374151 + public let colorsGray700 = UIColor(hex: 0x374151FF) + /// #1f2937 + public let colorsGray800 = UIColor(hex: 0x1F2937FF) + /// #111827 + public let colorsGray900 = UIColor(hex: 0x111827FF) + /// #fafafa + public let colorsZinc50 = UIColor(hex: 0xFAFAFAFF) + /// #f4f4f5 + public let colorsZinc100 = UIColor(hex: 0xF4F4F5FF) + /// #e4e4e7 + public let colorsZinc200 = UIColor(hex: 0xE4E4E7FF) + /// #d4d4d8 + public let colorsZinc300 = UIColor(hex: 0xD4D4D8FF) + /// #a1a1aa + public let colorsZinc400 = UIColor(hex: 0xA1A1AAFF) + /// #71717a + public let colorsZinc500 = UIColor(hex: 0x71717AFF) + /// #52525b + public let colorsZinc600 = UIColor(hex: 0x52525BFF) + /// #3f3f46 + public let colorsZinc700 = UIColor(hex: 0x3F3F46FF) + /// #27272a + public let colorsZinc800 = UIColor(hex: 0x27272AFF) + /// #18181b + public let colorsZinc900 = UIColor(hex: 0x18181BFF) + /// #fafafa + public let colorsNeutral50 = UIColor(hex: 0xFAFAFAFF) + /// #f5f5f5 + public let colorsNeutral100 = UIColor(hex: 0xF5F5F5FF) + /// #e5e5e5 + public let colorsNeutral200 = UIColor(hex: 0xE5E5E5FF) + /// #d4d4d4 + public let colorsNeutral300 = UIColor(hex: 0xD4D4D4FF) + /// #a3a3a3 + public let colorsNeutral400 = UIColor(hex: 0xA3A3A3FF) + /// #737373 + public let colorsNeutral500 = UIColor(hex: 0x737373FF) + /// #525252 + public let colorsNeutral600 = UIColor(hex: 0x525252FF) + /// #404040 + public let colorsNeutral700 = UIColor(hex: 0x404040FF) + /// #262626 + public let colorsNeutral800 = UIColor(hex: 0x262626FF) + /// #171717 + public let colorsNeutral900 = UIColor(hex: 0x171717FF) + /// #fafaf9 + public let colorsStone50 = UIColor(hex: 0xFAFAF9FF) + /// #f5f5f4 + public let colorsStone100 = UIColor(hex: 0xF5F5F4FF) + /// #e7e5e4 + public let colorsStone200 = UIColor(hex: 0xE7E5E4FF) + /// #d6d3d1 + public let colorsStone300 = UIColor(hex: 0xD6D3D1FF) + /// #a8a29e + public let colorsStone400 = UIColor(hex: 0xA8A29EFF) + /// #78716c + public let colorsStone500 = UIColor(hex: 0x78716CFF) + /// #57534e + public let colorsStone600 = UIColor(hex: 0x57534EFF) + /// #44403c + public let colorsStone700 = UIColor(hex: 0x44403CFF) + /// #292524 + public let colorsStone800 = UIColor(hex: 0x292524FF) + /// #1c1917 + public let colorsStone900 = UIColor(hex: 0x1C1917FF) + /// #fef2f2 + public let colorsRed50 = UIColor(hex: 0xFEF2F2FF) + /// #fee2e2 + public let colorsRed100 = UIColor(hex: 0xFEE2E2FF) + /// #fecaca + public let colorsRed200 = UIColor(hex: 0xFECACAFF) + /// #fca5a5 + public let colorsRed300 = UIColor(hex: 0xFCA5A5FF) + /// #f87171 + public let colorsRed400 = UIColor(hex: 0xF87171FF) + /// #ef4444 + public let colorsRed500 = UIColor(hex: 0xEF4444FF) + /// #dc2626 + public let colorsRed600 = UIColor(hex: 0xDC2626FF) + /// #b91c1c + public let colorsRed700 = UIColor(hex: 0xB91C1CFF) + /// #991b1b + public let colorsRed800 = UIColor(hex: 0x991B1BFF) + /// #7f1d1d + public let colorsRed900 = UIColor(hex: 0x7F1D1DFF) + /// #fff7ed + public let colorsOrange50 = UIColor(hex: 0xFFF7EDFF) + /// #ffedd5 + public let colorsOrange100 = UIColor(hex: 0xFFEDD5FF) + /// #fed7aa + public let colorsOrange200 = UIColor(hex: 0xFED7AAFF) + /// #fdba74 + public let colorsOrange300 = UIColor(hex: 0xFDBA74FF) + /// #fb923c + public let colorsOrange400 = UIColor(hex: 0xFB923CFF) + /// #f97316 + public let colorsOrange500 = UIColor(hex: 0xF97316FF) + /// #ea580c + public let colorsOrange600 = UIColor(hex: 0xEA580CFF) + /// #c2410c + public let colorsOrange700 = UIColor(hex: 0xC2410CFF) + /// #9a3412 + public let colorsOrange800 = UIColor(hex: 0x9A3412FF) + /// #7c2d12 + public let colorsOrange900 = UIColor(hex: 0x7C2D12FF) + /// #fffbeb + public let colorsAmber50 = UIColor(hex: 0xFFFBEBFF) + /// #fef3c7 + public let colorsAmber100 = UIColor(hex: 0xFEF3C7FF) + /// #fde68a + public let colorsAmber200 = UIColor(hex: 0xFDE68AFF) + /// #fcd34d + public let colorsAmber300 = UIColor(hex: 0xFCD34DFF) + /// #fbbf24 + public let colorsAmber400 = UIColor(hex: 0xFBBF24FF) + /// #f59e0b + public let colorsAmber500 = UIColor(hex: 0xF59E0BFF) + /// #d97706 + public let colorsAmber600 = UIColor(hex: 0xD97706FF) + /// #b45309 + public let colorsAmber700 = UIColor(hex: 0xB45309FF) + /// #92400e + public let colorsAmber800 = UIColor(hex: 0x92400EFF) + /// #78350f + public let colorsAmber900 = UIColor(hex: 0x78350FFF) + /// #fefce8 + public let colorsYellow50 = UIColor(hex: 0xFEFCE8FF) + /// #fef9c3 + public let colorsYellow100 = UIColor(hex: 0xFEF9C3FF) + /// #fef08a + public let colorsYellow200 = UIColor(hex: 0xFEF08AFF) + /// #fde047 + public let colorsYellow300 = UIColor(hex: 0xFDE047FF) + /// #facc15 + public let colorsYellow400 = UIColor(hex: 0xFACC15FF) + /// #eab308 + public let colorsYellow500 = UIColor(hex: 0xEAB308FF) + /// #ca8a04 + public let colorsYellow600 = UIColor(hex: 0xCA8A04FF) + /// #a16207 + public let colorsYellow700 = UIColor(hex: 0xA16207FF) + /// #854d0e + public let colorsYellow800 = UIColor(hex: 0x854D0EFF) + /// #713f12 + public let colorsYellow900 = UIColor(hex: 0x713F12FF) + /// #f7fee7 + public let colorsLime50 = UIColor(hex: 0xF7FEE7FF) + /// #ecfccb + public let colorsLime100 = UIColor(hex: 0xECFCCBFF) + /// #d9f99d + public let colorsLime200 = UIColor(hex: 0xD9F99DFF) + /// #bef264 + public let colorsLime300 = UIColor(hex: 0xBEF264FF) + /// #a3e635 + public let colorsLime400 = UIColor(hex: 0xA3E635FF) + /// #84cc16 + public let colorsLime500 = UIColor(hex: 0x84CC16FF) + /// #65a30d + public let colorsLime600 = UIColor(hex: 0x65A30DFF) + /// #4d7c0f + public let colorsLime700 = UIColor(hex: 0x4D7C0FFF) + /// #3f6212 + public let colorsLime800 = UIColor(hex: 0x3F6212FF) + /// #365314 + public let colorsLime900 = UIColor(hex: 0x365314FF) + /// #f0fdf4 + public let colorsGreen50 = UIColor(hex: 0xF0FDF4FF) + /// #dcfce7 + public let colorsGreen100 = UIColor(hex: 0xDCFCE7FF) + /// #bbf7d0 + public let colorsGreen200 = UIColor(hex: 0xBBF7D0FF) + /// #86efac + public let colorsGreen300 = UIColor(hex: 0x86EFACFF) + /// #4ade80 + public let colorsGreen400 = UIColor(hex: 0x4ADE80FF) + /// #22c55e + public let colorsGreen500 = UIColor(hex: 0x22C55EFF) + /// #16a34a + public let colorsGreen600 = UIColor(hex: 0x16A34AFF) + /// #15803d + public let colorsGreen700 = UIColor(hex: 0x15803DFF) + /// #166534 + public let colorsGreen800 = UIColor(hex: 0x166534FF) + /// #14532d + public let colorsGreen900 = UIColor(hex: 0x14532DFF) + /// #ecfdf5 + public let colorsEmerald50 = UIColor(hex: 0xECFDF5FF) + /// #d1fae5 + public let colorsEmerald100 = UIColor(hex: 0xD1FAE5FF) + /// #a7f3d0 + public let colorsEmerald200 = UIColor(hex: 0xA7F3D0FF) + /// #6ee7b7 + public let colorsEmerald300 = UIColor(hex: 0x6EE7B7FF) + /// #34d399 + public let colorsEmerald400 = UIColor(hex: 0x34D399FF) + /// #10b981 + public let colorsEmerald500 = UIColor(hex: 0x10B981FF) + /// #059669 + public let colorsEmerald600 = UIColor(hex: 0x059669FF) + /// #047857 + public let colorsEmerald700 = UIColor(hex: 0x047857FF) + /// #065f46 + public let colorsEmerald800 = UIColor(hex: 0x065F46FF) + /// #064e3b + public let colorsEmerald900 = UIColor(hex: 0x064E3BFF) + /// #f0fdfa + public let colorsTeal50 = UIColor(hex: 0xF0FDFAFF) + /// #ccfbf1 + public let colorsTeal100 = UIColor(hex: 0xCCFBF1FF) + /// #99f6e4 + public let colorsTeal200 = UIColor(hex: 0x99F6E4FF) + /// #5eead4 + public let colorsTeal300 = UIColor(hex: 0x5EEAD4FF) + /// #2dd4bf + public let colorsTeal400 = UIColor(hex: 0x2DD4BFFF) + /// #14b8a6 + public let colorsTeal500 = UIColor(hex: 0x14B8A6FF) + /// #0d9488 + public let colorsTeal600 = UIColor(hex: 0x0D9488FF) + /// #0f766e + public let colorsTeal700 = UIColor(hex: 0x0F766EFF) + /// #115e59 + public let colorsTeal800 = UIColor(hex: 0x115E59FF) + /// #134e4a + public let colorsTeal900 = UIColor(hex: 0x134E4AFF) + /// #ecfeff + public let colorsCyan50 = UIColor(hex: 0xECFEFFFF) + /// #cffafe + public let colorsCyan100 = UIColor(hex: 0xCFFAFEFF) + /// #a5f3fc + public let colorsCyan200 = UIColor(hex: 0xA5F3FCFF) + /// #67e8f9 + public let colorsCyan300 = UIColor(hex: 0x67E8F9FF) + /// #22d3ee + public let colorsCyan400 = UIColor(hex: 0x22D3EEFF) + /// #06b6d4 + public let colorsCyan500 = UIColor(hex: 0x06B6D4FF) + /// #0891b2 + public let colorsCyan600 = UIColor(hex: 0x0891B2FF) + /// #0e7490 + public let colorsCyan700 = UIColor(hex: 0x0E7490FF) + /// #155e75 + public let colorsCyan800 = UIColor(hex: 0x155E75FF) + /// #164e63 + public let colorsCyan900 = UIColor(hex: 0x164E63FF) + /// #f0f9ff + public let colorsSky50 = UIColor(hex: 0xF0F9FFFF) + /// #e0f2fe + public let colorsSky100 = UIColor(hex: 0xE0F2FEFF) + /// #bae6fd + public let colorsSky200 = UIColor(hex: 0xBAE6FDFF) + /// #7dd3fc + public let colorsSky300 = UIColor(hex: 0x7DD3FCFF) + /// #38bdf8 + public let colorsSky400 = UIColor(hex: 0x38BDF8FF) + /// #0ea5e9 + public let colorsSky500 = UIColor(hex: 0x0EA5E9FF) + /// #0284c7 + public let colorsSky600 = UIColor(hex: 0x0284C7FF) + /// #0369a1 + public let colorsSky700 = UIColor(hex: 0x0369A1FF) + /// #075985 + public let colorsSky800 = UIColor(hex: 0x075985FF) + /// #0c4a6e + public let colorsSky900 = UIColor(hex: 0x0C4A6EFF) + /// #eff6ff + public let colorsBlue50 = UIColor(hex: 0xEFF6FFFF) + /// #dbeafe + public let colorsBlue100 = UIColor(hex: 0xDBEAFEFF) + /// #bfdbfe + public let colorsBlue200 = UIColor(hex: 0xBFDBFEFF) + /// #93c5fd + public let colorsBlue300 = UIColor(hex: 0x93C5FDFF) + /// #60a5fa + public let colorsBlue400 = UIColor(hex: 0x60A5FAFF) + /// #3b82f6 + public let colorsBlue500 = UIColor(hex: 0x3B82F6FF) + /// #2563eb + public let colorsBlue600 = UIColor(hex: 0x2563EBFF) + /// #1d4ed8 + public let colorsBlue700 = UIColor(hex: 0x1D4ED8FF) + /// #1e40af + public let colorsBlue800 = UIColor(hex: 0x1E40AFFF) + /// #1e3a8a + public let colorsBlue900 = UIColor(hex: 0x1E3A8AFF) + /// #eef2ff + public let colorsIndigo50 = UIColor(hex: 0xEEF2FFFF) + /// #e0e7ff + public let colorsIndigo100 = UIColor(hex: 0xE0E7FFFF) + /// #c7d2fe + public let colorsIndigo200 = UIColor(hex: 0xC7D2FEFF) + /// #a5b4fc + public let colorsIndigo300 = UIColor(hex: 0xA5B4FCFF) + /// #818cf8 + public let colorsIndigo400 = UIColor(hex: 0x818CF8FF) + /// #6366f1 + public let colorsIndigo500 = UIColor(hex: 0x6366F1FF) + /// #4f46e5 + public let colorsIndigo600 = UIColor(hex: 0x4F46E5FF) + /// #4338ca + public let colorsIndigo700 = UIColor(hex: 0x4338CAFF) + /// #3730a3 + public let colorsIndigo800 = UIColor(hex: 0x3730A3FF) + /// #312e81 + public let colorsIndigo900 = UIColor(hex: 0x312E81FF) + /// #f5f3ff + public let colorsViolet50 = UIColor(hex: 0xF5F3FFFF) + /// #ede9fe + public let colorsViolet100 = UIColor(hex: 0xEDE9FEFF) + /// #ddd6fe + public let colorsViolet200 = UIColor(hex: 0xDDD6FEFF) + /// #c4b5fd + public let colorsViolet300 = UIColor(hex: 0xC4B5FDFF) + /// #a78bfa + public let colorsViolet400 = UIColor(hex: 0xA78BFAFF) + /// #8b5cf6 + public let colorsViolet500 = UIColor(hex: 0x8B5CF6FF) + /// #7c3aed + public let colorsViolet600 = UIColor(hex: 0x7C3AEDFF) + /// #6d28d9 + public let colorsViolet700 = UIColor(hex: 0x6D28D9FF) + /// #5b21b6 + public let colorsViolet800 = UIColor(hex: 0x5B21B6FF) + /// #4c1d95 + public let colorsViolet900 = UIColor(hex: 0x4C1D95FF) + /// #faf5ff + public let colorsPurple50 = UIColor(hex: 0xFAF5FFFF) + /// #f3e8ff + public let colorsPurple100 = UIColor(hex: 0xF3E8FFFF) + /// #e9d5ff + public let colorsPurple200 = UIColor(hex: 0xE9D5FFFF) + /// #d8b4fe + public let colorsPurple300 = UIColor(hex: 0xD8B4FEFF) + /// #c084fc + public let colorsPurple400 = UIColor(hex: 0xC084FCFF) + /// #a855f7 + public let colorsPurple500 = UIColor(hex: 0xA855F7FF) + /// #9333ea + public let colorsPurple600 = UIColor(hex: 0x9333EAFF) + /// #7e22ce + public let colorsPurple700 = UIColor(hex: 0x7E22CEFF) + /// #6b21a8 + public let colorsPurple800 = UIColor(hex: 0x6B21A8FF) + /// #581c87 + public let colorsPurple900 = UIColor(hex: 0x581C87FF) + /// #fdf4ff + public let colorsFuschia50 = UIColor(hex: 0xFDF4FFFF) + /// #fae8ff + public let colorsFuschia100 = UIColor(hex: 0xFAE8FFFF) + /// #f5d0fe + public let colorsFuschia200 = UIColor(hex: 0xF5D0FEFF) + /// #f0abfc + public let colorsFuschia300 = UIColor(hex: 0xF0ABFCFF) + /// #e879f9 + public let colorsFuschia400 = UIColor(hex: 0xE879F9FF) + /// #d946ef + public let colorsFuschia500 = UIColor(hex: 0xD946EFFF) + /// #c026d3 + public let colorsFuschia600 = UIColor(hex: 0xC026D3FF) + /// #a21caf + public let colorsFuschia700 = UIColor(hex: 0xA21CAFFF) + /// #86198f + public let colorsFuschia800 = UIColor(hex: 0x86198FFF) + /// #701a75 + public let colorsFuschia900 = UIColor(hex: 0x701A75FF) + /// #fdf2f8 + public let colorsPink50 = UIColor(hex: 0xFDF2F8FF) + /// #fce7f3 + public let colorsPink100 = UIColor(hex: 0xFCE7F3FF) + /// #fbcfe8 + public let colorsPink200 = UIColor(hex: 0xFBCFE8FF) + /// #f9a8d4 + public let colorsPink300 = UIColor(hex: 0xF9A8D4FF) + /// #f472b6 + public let colorsPink400 = UIColor(hex: 0xF472B6FF) + /// #ec4899 + public let colorsPink500 = UIColor(hex: 0xEC4899FF) + /// #db2777 + public let colorsPink600 = UIColor(hex: 0xDB2777FF) + /// #be185d + public let colorsPink700 = UIColor(hex: 0xBE185DFF) + /// #9d174d + public let colorsPink800 = UIColor(hex: 0x9D174DFF) + /// #831843 + public let colorsPink900 = UIColor(hex: 0x831843FF) + /// #fff1f2 + public let colorsRose50 = UIColor(hex: 0xFFF1F2FF) + /// #ffe4e6 + public let colorsRose100 = UIColor(hex: 0xFFE4E6FF) + /// #fecdd3 + public let colorsRose200 = UIColor(hex: 0xFECDD3FF) + /// #fda4af + public let colorsRose300 = UIColor(hex: 0xFDA4AFFF) + /// #fb7185 + public let colorsRose400 = UIColor(hex: 0xFB7185FF) + /// #f43f5e + public let colorsRose500 = UIColor(hex: 0xF43F5EFF) + /// #e11d48 + public let colorsRose600 = UIColor(hex: 0xE11D48FF) + /// #be123c + public let colorsRose700 = UIColor(hex: 0xBE123CFF) + /// #9f1239 + public let colorsRose800 = UIColor(hex: 0x9F1239FF) + /// #881337 + public let colorsRose900 = UIColor(hex: 0x881337FF) +} + +private extension UIColor { + + convenience init(hex: UInt32) { + let red = UInt8((hex >> 24) & 0xFF) + let green = UInt8((hex >> 16) & 0xFF) + let blue = UInt8((hex >> 8) & 0xFF) + let alpha = UInt8(hex & 0xFF) + + self.init( + red: CGFloat(red) / 255.0, + green: CGFloat(green) / 255.0, + blue: CGFloat(blue) / 255.0, + alpha: CGFloat(alpha) / 255.0 + ) + } +} diff --git a/Demo/FigmaGenDemo/Generated/ColorTokens.swift b/Demo/FigmaGenDemo/Generated/ColorTokens.swift new file mode 100644 index 0000000..edb82ef --- /dev/null +++ b/Demo/FigmaGenDemo/Generated/ColorTokens.swift @@ -0,0 +1,77 @@ +// swiftlint:disable all +// Generated using FigmaGen - https://github.com/hhru/FigmaGen + +#if canImport(UIKit) +import UIKit +#else +import AppKit +#endif + +public struct ColorTokens { + public struct Accent { + /// accent.bg + /// + /// Day: #c3dafe + /// Night: #434190 + public let bg: UIColor + /// accent.default + /// + /// Day: #7f9cf5 + /// Night: #5a67d8 + public let `default`: UIColor + /// accent.onAccent + /// + /// Day: #ffffff + /// Night: #ffffff + public let onAccent: UIColor + } + + public let accent: Accent + public struct Bg { + /// bg.default + /// + /// Day: #ffffff + /// Night: #1a202c + public let `default`: UIColor + /// bg.muted + /// + /// Day: #f7fafc + /// Night: #4a5568 + public let muted: UIColor + /// bg.subtle + /// + /// Day: #edf2f7 + /// Night: #718096 + public let subtle: UIColor + } + + public let bg: Bg + public struct Fg { + /// fg.default + /// + /// Day: #000000 + /// Night: #ffffff + public let `default`: UIColor + /// fg.muted + /// + /// Day: #4a5568 + /// Night: #e2e8f0 + public let muted: UIColor + /// fg.subtle + /// + /// Day: #a0aec0 + /// Night: #a0aec0 + public let subtle: UIColor + } + + public let fg: Fg + public struct Shadows { + /// shadows.default + /// + /// Day: #1a202c + /// Night: #00000000 + public let `default`: UIColor + } + + public let shadows: Shadows +} diff --git a/Sources/FigmaGen/Commands/TokensCommand.swift b/Sources/FigmaGen/Commands/TokensCommand.swift index 30ddd38..2458b4d 100644 --- a/Sources/FigmaGen/Commands/TokensCommand.swift +++ b/Sources/FigmaGen/Commands/TokensCommand.swift @@ -108,12 +108,12 @@ extension TokensCommand { file: resolveFileConfiguration(), accessToken: resolveAccessTokenConfiguration(), templates: TokensTemplateConfiguration( - color: TokensTemplateConfiguration.Template( + colors: TokensTemplateConfiguration.Template( template: colorsTemplate.value, templateOptions: resolveTemplateOptions(colorsTemplateOptions.value), destination: colorsDestination.value ), - baseColor: TokensTemplateConfiguration.Template( + baseColors: TokensTemplateConfiguration.Template( template: baseColorsTemplate.value, templateOptions: resolveTemplateOptions(baseColorsTemplateOptions.value), destination: baseColorsDestination.value diff --git a/Sources/FigmaGen/Dependencies.swift b/Sources/FigmaGen/Dependencies.swift index da51ec1..b0174f5 100644 --- a/Sources/FigmaGen/Dependencies.swift +++ b/Sources/FigmaGen/Dependencies.swift @@ -89,7 +89,7 @@ enum Dependencies { StencilCollectionDropLastModificator(), StencilCollectionRemovingFirstModificator(), StencilHexToAlphaFilter(), - StencilFullHexFilter() + StencilFullHexModificator() ] static let templateRenderer: TemplateRenderer = DefaultTemplateRenderer( @@ -119,14 +119,6 @@ enum Dependencies { templateRenderer: templateRenderer ) - static let libraryGenerator: LibraryGenerator = DefaultLibraryGenerator( - configurationProvider: configurationProvider, - colorStylesGenerator: colorStylesGenerator, - textStylesGenerator: textStylesGenerator, - imagesGenerator: imagesGenerator, - shadowStylesGenerator: shadowStylesGenerator - ) - static let colorTokensGenerator: ColorTokensGenerator = DefaultColorTokensGenerator( tokensResolver: tokensResolver, templateRenderer: templateRenderer @@ -143,4 +135,13 @@ enum Dependencies { colorTokensGenerator: colorTokensGenerator, baseColorTokensGenerator: baseColorTokensGenerator ) + + static let libraryGenerator: LibraryGenerator = DefaultLibraryGenerator( + configurationProvider: configurationProvider, + colorStylesGenerator: colorStylesGenerator, + textStylesGenerator: textStylesGenerator, + imagesGenerator: imagesGenerator, + shadowStylesGenerator: shadowStylesGenerator, + tokensGenerator: tokensGenerator + ) } diff --git a/Sources/FigmaGen/Generators/Library/DefaultLibraryGenerator.swift b/Sources/FigmaGen/Generators/Library/DefaultLibraryGenerator.swift index 8b485fd..c8a1727 100644 --- a/Sources/FigmaGen/Generators/Library/DefaultLibraryGenerator.swift +++ b/Sources/FigmaGen/Generators/Library/DefaultLibraryGenerator.swift @@ -10,6 +10,7 @@ final class DefaultLibraryGenerator: LibraryGenerator { let textStylesGenerator: TextStylesGenerator let imagesGenerator: ImagesGenerator let shadowStylesGenerator: ShadowStylesGenerator + let tokensGenerator: TokensGenerator // MARK: - Initializers @@ -18,13 +19,15 @@ final class DefaultLibraryGenerator: LibraryGenerator { colorStylesGenerator: ColorStylesGenerator, textStylesGenerator: TextStylesGenerator, imagesGenerator: ImagesGenerator, - shadowStylesGenerator: ShadowStylesGenerator + shadowStylesGenerator: ShadowStylesGenerator, + tokensGenerator: TokensGenerator ) { self.configurationProvider = configurationProvider self.colorStylesGenerator = colorStylesGenerator self.textStylesGenerator = textStylesGenerator self.imagesGenerator = imagesGenerator self.shadowStylesGenerator = shadowStylesGenerator + self.tokensGenerator = tokensGenerator } // MARK: - Instance Methods @@ -48,6 +51,10 @@ final class DefaultLibraryGenerator: LibraryGenerator { promises.append(shadowStylesGenerator.generate(configuration: shadowStylesConfiguration)) } + if let tokensConfiguration = configuration.resolveTokens() { + promises.append(Promise { try await self.tokensGenerator.generate(configuration: tokensConfiguration) }) + } + return when(fulfilled: promises) } diff --git a/Sources/FigmaGen/Generators/Tokens/GenerationParametersResolver/DefaultTokensGenerationParametersResolver.swift b/Sources/FigmaGen/Generators/Tokens/GenerationParametersResolver/DefaultTokensGenerationParametersResolver.swift index 3554f6e..4d7f3d7 100644 --- a/Sources/FigmaGen/Generators/Tokens/GenerationParametersResolver/DefaultTokensGenerationParametersResolver.swift +++ b/Sources/FigmaGen/Generators/Tokens/GenerationParametersResolver/DefaultTokensGenerationParametersResolver.swift @@ -73,12 +73,12 @@ final class DefaultTokensGenerationParametersResolver: TokensGenerationParameter ) let colorRender = resolveRenderParameters( - template: configuration.templates?.color, + template: configuration.templates?.colors, nativeTemplateName: "ColorTokens" ) let baseColorRender = resolveRenderParameters( - template: configuration.templates?.baseColor, + template: configuration.templates?.baseColors, nativeTemplateName: "BaseColorTokens" ) diff --git a/Sources/FigmaGen/Generators/Tokens/Generators/Color/DefaultColorTokensGenerator.swift b/Sources/FigmaGen/Generators/Tokens/Generators/Color/DefaultColorTokensGenerator.swift index 388cce1..a3e975b 100644 --- a/Sources/FigmaGen/Generators/Tokens/Generators/Color/DefaultColorTokensGenerator.swift +++ b/Sources/FigmaGen/Generators/Tokens/Generators/Color/DefaultColorTokensGenerator.swift @@ -107,7 +107,7 @@ final class DefaultColorTokensGenerator: ColorTokensGenerator { ) ), name: tokenName, - path: Array(path.dropFirst()) + path: path ) } @@ -121,7 +121,7 @@ final class DefaultColorTokensGenerator: ColorTokensGenerator { let path = token.name.components(separatedBy: ".") - guard path[0] == "color" else { + guard path[0] != "gradient" else { return nil } diff --git a/Sources/FigmaGen/Generators/Tokens/Resolver/DefaultTokensResolver.swift b/Sources/FigmaGen/Generators/Tokens/Resolver/DefaultTokensResolver.swift index 6e5d476..9ad3303 100644 --- a/Sources/FigmaGen/Generators/Tokens/Resolver/DefaultTokensResolver.swift +++ b/Sources/FigmaGen/Generators/Tokens/Resolver/DefaultTokensResolver.swift @@ -28,9 +28,8 @@ final class DefaultTokensResolver: TokensResolver { } let hexFloat = CGFloat(hexComponent) - let floatValue = CGFloat(hexFloat / 255.0) - return floatValue + return hexFloat / 255.0 } private func makeColor(hex: String, alpha: CGFloat) throws -> Color { @@ -118,7 +117,7 @@ final class DefaultTokensResolver: TokensResolver { let resolvedValue = try resolveValue(value, tokenValues: tokenValues) if resolvedValue.hasPrefix("#") { - return value + return resolvedValue } return try resolveColorValue(resolvedValue, tokenValues: tokenValues).hexString diff --git a/Sources/FigmaGen/Models/Configuration/Configuration.swift b/Sources/FigmaGen/Models/Configuration/Configuration.swift index 921a698..959a9dc 100644 --- a/Sources/FigmaGen/Models/Configuration/Configuration.swift +++ b/Sources/FigmaGen/Models/Configuration/Configuration.swift @@ -10,6 +10,7 @@ struct Configuration: Decodable { let textStyles: TextStylesConfiguration? let images: ImagesConfiguration? let shadowStyles: ShadowStylesConfiguration? + let tokens: TokensConfiguration? // MARK: - Instance Methods @@ -28,4 +29,8 @@ struct Configuration: Decodable { func resolveShadowStyles() -> ShadowStylesConfiguration? { shadowStyles?.resolve(base: base) } + + func resolveTokens() -> TokensConfiguration? { + tokens?.resolve(base: base) + } } diff --git a/Sources/FigmaGen/Models/Configuration/Tokens/TokensConfiguration.swift b/Sources/FigmaGen/Models/Configuration/Tokens/TokensConfiguration.swift index a430b5d..aefe0ac 100644 --- a/Sources/FigmaGen/Models/Configuration/Tokens/TokensConfiguration.swift +++ b/Sources/FigmaGen/Models/Configuration/Tokens/TokensConfiguration.swift @@ -36,4 +36,18 @@ struct TokensConfiguration: Decodable { self.accessToken = accessToken self.templates = templates } + + // MARK: - Instance Methods + + func resolve(base: BaseConfiguration?) -> Self { + guard let base else { + return self + } + + return Self( + file: file ?? base.file, + accessToken: accessToken ?? base.accessToken, + templates: templates + ) + } } diff --git a/Sources/FigmaGen/Models/Configuration/Tokens/TokensTemplateConfiguration.swift b/Sources/FigmaGen/Models/Configuration/Tokens/TokensTemplateConfiguration.swift index 4995ade..a96a257 100644 --- a/Sources/FigmaGen/Models/Configuration/Tokens/TokensTemplateConfiguration.swift +++ b/Sources/FigmaGen/Models/Configuration/Tokens/TokensTemplateConfiguration.swift @@ -28,8 +28,8 @@ struct TokensTemplateConfiguration: Decodable { // MARK: - Instance Properties - let color: Template? - let baseColor: Template? + let colors: Template? + let baseColors: Template? } extension TokensTemplateConfiguration.Template { diff --git a/Sources/FigmaGen/Models/Token/TokensStudioPluginData.swift b/Sources/FigmaGen/Models/Token/TokensStudioPluginData.swift index a7936d1..97cc87a 100644 --- a/Sources/FigmaGen/Models/Token/TokensStudioPluginData.swift +++ b/Sources/FigmaGen/Models/Token/TokensStudioPluginData.swift @@ -8,7 +8,6 @@ struct TokensStudioPluginData: Codable, Hashable { // MARK: - Instance Properties - let persistentNodesCache: String let version: String let values: String let usedTokenSet: String diff --git a/Sources/FigmaGenTools/Extensions/Promise+Extensions.swift b/Sources/FigmaGenTools/Extensions/Promise+Extensions.swift index f234c48..49347e1 100644 --- a/Sources/FigmaGenTools/Extensions/Promise+Extensions.swift +++ b/Sources/FigmaGenTools/Extensions/Promise+Extensions.swift @@ -9,6 +9,21 @@ extension Promise { Promise(error: error) } + // MARK: - Initializers + + public convenience init(asyncFunc: @escaping () async throws -> T) { + self.init { resolver in + Task { + do { + let result = try await asyncFunc() + resolver.fulfill(result) + } catch { + resolver.reject(error) + } + } + } + } + // MARK: - Instance Methods public func nest( diff --git a/Templates/BaseColorTokens.stencil b/Templates/BaseColorTokens.stencil index 8f3f249..58720db 100644 --- a/Templates/BaseColorTokens.stencil +++ b/Templates/BaseColorTokens.stencil @@ -14,12 +14,9 @@ import AppKit {{ accessModifier }} struct {{ tokenTypeName }} { {% for color in colors %} /// {{ color.value }} - {{ accessModifier }} let {% call propertyName color.path|removingFirst:"color"|removingFirst:"base"|join:"." %} = {{ colorTypeName }}(hex: 0x{{ color.value|replace:"#",""|uppercase }}) + {{ accessModifier }} let {% call propertyName color.path|removingFirst:"color"|removingFirst:"base"|join:"." %} = {{ colorTypeName }}(hex: 0x{{ color.value|fullHex|replace:"#","" }}) {% endfor %} } -{% else %} -// No base color tokens found -{% endif %} private extension {{ colorTypeName }} { @@ -36,4 +33,7 @@ private extension {{ colorTypeName }} { alpha: CGFloat(alpha) / 255.0 ) } -} \ No newline at end of file +} +{% else %} +// No base color tokens found +{% endif %} \ No newline at end of file diff --git a/Templates/ColorTokens.stencil b/Templates/ColorTokens.stencil index 6213e44..0693972 100644 --- a/Templates/ColorTokens.stencil +++ b/Templates/ColorTokens.stencil @@ -1,4 +1,5 @@ {% include "FileHeader.stencil" %} +{% if colorTokens %} {% set colorTypeName %}{{ options.colorTypeName|default:"UIColor" }}{% endset %} {% set accessModifier %}{% if options.publicAccess %}public{% else %}internal{% endif %}{% endset %} {% set tokenTypeName %}{{ options.tokenTypeName|default:"ColorTokens" }}{% endset %} @@ -6,22 +7,20 @@ {% macro typeName name %}{{ name|swiftIdentifier:"pretty"|upperFirstLetter|escapeReservedKeywords }}{% endmacro %} {% macro recursiveBlock item %} {% for color in item.colors %} - /// {{ color.name }} /// /// Day: {{ color.dayTheme.value }} /// Night: {{ color.nightTheme.value }} - public let {% call propertyName color.path.last %}: {{ colorTypeName }} + {{ accessModifier }} let {% call propertyName color.path.last %}: {{ colorTypeName }} {% endfor %} {% for child in item.children %} - - public struct {% call typeName child.name %} { + {{ accessModifier }} struct {% call typeName child.name %} { {% filter indent:4 %} {% call recursiveBlock child %} {% endfilter %} } - public let {% call propertyName child.name %}: {% call typeName child.name %} + {{ accessModifier }} let {% call propertyName child.name %}: {% call typeName child.name %} {% endfor %} {% endmacro %} @@ -32,9 +31,8 @@ import AppKit #endif {{ accessModifier }} struct {{ tokenTypeName }} { - {% if colorTokens %} {% call recursiveBlock colorTokens %} - {% else %} - // No color tokens found - {% endif %} } +{% else %} +// No color tokens found +{% endif %}